Comparar commits

..

93 Commits

Autor SHA1 Mensagem Data
Ghostkeeper 6c931e2935 Fix modulo by size of lines
All lines have size 2, which is why it was never a problem, but in theory if the polygon has a different line size it will now work correctly.

Contributes to issue CURA-329.
2016-02-03 12:55:19 +01:00
Ghostkeeper 295a5a9fc4 Factor out points of insert distance calculations
These points are factored out to intermediary variables to reduce width of the code at the cost of height.

Contributes to issue CURA-329.
2016-02-03 12:55:14 +01:00
Tim Kuipers cc8dadf571 fix: variable name shadowing in pathorderOptimizer (CURA-329) 2016-02-03 12:55:09 +01:00
Ghostkeeper 118e71c7af Cache lines for simpler expressions
Some of these expressions were deemed too complex to understand. In an effort to make them simpler, more sloc were added.

Contributes to issue CURA-329.
2016-02-03 12:54:55 +01:00
Ghostkeeper b5d29a66b2 Fix modular arithmetic with negative input
Stupid ISO14882. Modular arithmetic would define that -2 % 7 results in 5, not -2. But C++ implements modulus as keeping the same sign as the left-hand parameter. Thus we have to make sure the left parameter is always positive.

Contributes to issue CURA-329.
2016-02-03 12:54:52 +01:00
Ghostkeeper e937e98b27 Extract trying to insert a line in a cluster to function
I don't wholly agree with this change, since it basically has to transfer the entire context to a separate function of which the functionality is not entirely separate. But at least it makes the cluster() function a bit shorter.

Contributes to issue CURA-329.
2016-02-03 12:54:49 +01:00
Ghostkeeper c3705604cd Extract trying to insert a line in a cluster to function
I don't wholly agree with this change, since it basically has to transfer the entire context to a separate function of which the functionality is not entirely separate. But at least it makes the cluster() function a bit shorter.

Contributes to issue CURA-329.
2016-02-03 12:54:46 +01:00
Ghostkeeper e1993a8ccd Remove TODOs: No action required.
It has been decided and these outlined functions will stay this way.

Contributes to issue CURA-329.
2016-02-03 12:54:43 +01:00
Ghostkeeper b819f696eb Revert Add function to check if two lines are near to each other
Was kind of a superfluous function.

Contributes to issue CURA-329.
2016-02-03 12:54:39 +01:00
Ghostkeeper b1b37cf19b Rename orientation to orientation_index
If the type is not visible directly in the code, this clarifies that it is supposed to be an index in an array.

Contributes to issue CURA-329.
2016-02-03 12:54:36 +01:00
Ghostkeeper 9808be9dfa Cache first and last line indices in get_orientations
No need to fetch them from the vector every time. This is faster if it wouldn't be optimised out by the compiler, and results in more readable code.

Contributes to issue CURA-329.
2016-02-03 12:54:34 +01:00
Ghostkeeper 06e8848ee2 Rename shuffle to shuffled
The vector that holds the shuffled waypoints holds the shuffled stuff, so call it shuffled.

Contributes to issue CURA-329.
2016-02-03 12:54:31 +01:00
Ghostkeeper c43d5ccbf3 Fix codestyle violations
These codestyle violations shouldn't be fixed here since it ruins the git history, but it seems this change isn't getting through without formatting. Therefore I fix them now. Fixed using Netbeans' auto-formatter (and checked manually: it rightly interpreted template brackets as binary operators but we don't see them that way).

Contributes to issue CURA-329.
2016-02-03 12:54:28 +01:00
Ghostkeeper e3e4ccdba8 Add function to check if two lines are near to each other
If they are, they should be placed in the same cluster. Currently this check is completely superfluous since the same check needs to be used to determine in what direction a line should be placed.

Contributes to issue CURA-329.
2016-02-03 12:54:25 +01:00
Ghostkeeper e0eacf0675 Remove unused header of checkIfLineIsBest
The implementation of this function was removed. The header should also have been removed.

Contributes to issue CURA-329.
2016-02-03 12:54:22 +01:00
Ghostkeeper ad1489e0ee Outline the procedures to insert waypoints
In the optimisation step, outline the procedures to insert elements in the resulting lists to their own functions. I really don't agree with this change, since I think it reduces readability as well as efficiency, but it was desired by code review to reduce the deepest indentation of the code, which is a good thing.

Contributes to issue CURA-329.
2016-02-03 12:54:19 +01:00
Ghostkeeper 3efe717154 Codestyle
The line in this loop was updated by my IDE's refactor, so I should've updated it to the code style.

Contributes to issue CURA-329.
2016-02-03 12:54:16 +01:00
Ghostkeeper 17bd906318 Rename LineOrderOptimizer's polygons to lines
They should only be lines. There is no check for it anywhere, though.

Contributes to issue CURA-329.
2016-02-03 12:54:14 +01:00
Ghostkeeper b8cdf0da24 Rename LineOrderOptimizer's polygons to lines
They should only be lines. There is no check for it anywhere, though.

Contributes to issue CURA-329.
2016-02-03 12:54:12 +01:00
Ghostkeeper 801641823c Update documentation
See that's the problem of having lots of documentation: The documentation may be out of date.

Contributes to issue CURA-329.
2016-02-03 12:54:09 +01:00
Ghostkeeper ac25e429b8 Update documentation of findPath.
I removed some old functions earlier but kept the wrong documentation and the documentation wasn't updated for the orientation change either.

Contributes to issue CURA-329.
2016-02-03 12:54:07 +01:00
Ghostkeeper 54e4b2d44e Create typedef Cluster for line clusters
A cluster is a vector of indices to the polygons vector.

Contributes to issue CURA-329.
2016-02-03 12:53:47 +01:00
Ghostkeeper 573699fd07 Extend the cluster() documentation
It describes how lines are precisely clustered and why.

Contributes to issue CURA-329.
2016-02-03 12:53:43 +01:00
Ghostkeeper 304a84a25c Introduce some newlines to improve readability
It should better segment the code into pieces.

Contributes to issue CURA-329.
2016-02-03 12:53:41 +01:00
Ghostkeeper 0e6412289c Update documentation of initial polyStart
Contributes to issue CURA-329.
2016-02-03 12:53:39 +01:00
Ghostkeeper e5901df8e1 Remove unused checkIfLineIsBest function
Penalising sharp corners is no longer applicable, since within clusters both endpoints of adjacent lines must be near each other, so the corner will always be approximately 180 degrees. Between clusters, combing deforms most of the travel moves which makes the corner angle calculation inaccurate.

Contributes to issue CURA-329.
2016-02-03 12:53:37 +01:00
Ghostkeeper e2f96fb1ad Document the usage of indices to polygons in LineOrderOptimizer
I can't document the real reason why indices are used instead of references. That is located in a different part of the code base that is much order.

Contributes to issue CURA-329.
2016-02-03 12:53:34 +01:00
Ghostkeeper 190908ab27 Store waypoint start/end points as pair
The pairs are termed "orientations". This actually makes the code a lot more self-documenting.

Contributes to issue CURA-329.
2016-02-03 12:53:22 +01:00
Ghostkeeper ff775d12f2 Separate declaration of functions from TSP solver declaration
The declaration of the TSP solver with two bulky anonymous functions is unwieldy. The functions are now defined separately and passed to the constructor of TSP.

Contributes to issue CURA-329.
2016-02-03 12:53:19 +01:00
Ghostkeeper a2fd4c0bf3 Document template types
The element template type was a source of confusion with respect to passing by value or by reference. It should be better documented now.

Contributes to issue CURA-329.
2016-02-03 12:53:17 +01:00
Ghostkeeper 404edc5cdc Code readability and documentation of orientation decoding
Where the optimal orientation is used to correctly place the line in order, the code could be made more readable.

Contributes to issue CURA-329.
2016-02-03 12:53:14 +01:00
Ghostkeeper de4edc36bb Fix mirroring when cluster size is 1
In the path order, the mirroring was incorrectly detected when cluster size is 1, since there are only 2 orientations for those clusters instead of 4.

Contributes to issue CURA-329.
2016-02-03 12:53:11 +01:00
Ghostkeeper 1623e58063 Improve clustering heuristic when not aligned
In grid infill, the infill lines are not necessarily aligned. This makes clustering more difficult, but this should help at no additional computational cost. The best distance is now only computed from the travel move that's to be made, while the check for both endpoints to be near remains in effect.

Contributes to issue CURA-329.
2016-02-03 12:53:09 +01:00
Ghostkeeper bb1c00f255 Smaller clustering grid size fallback
The clustering grid size should only be 0 if there is no infill. In those cases, the skin is the only part that needs clustering. For skin, a cluster size of 2mm is more appropriate.

Contributes to issue CURA-329.
2016-02-03 12:53:06 +01:00
Ghostkeeper 773e152454 Vary clustering grid size by infill density
This makes the algorithm run much faster and more reliably if the infill density is low.

Contributes to issue CURA-329.
2016-02-03 12:53:03 +01:00
Ghostkeeper 9c59eff999 Rename ListElement to WaypointListIterator
It technically points to an element, and is not the element itself. It is also used to iterate over the list, so yes it should be called an iterator.

Contributes to CURA-329.
2016-02-03 12:53:01 +01:00
Ghostkeeper b21b7e3115 Remove unused constructor
It used to be used to construct the empty waypoint for the starting_point, but that waypoint is no longer created.

Contributes to CURA-329.
2016-02-03 12:52:57 +01:00
Ghostkeeper 36b09753af Fix even/odd parity check for reversing elements
Because modulo 1 on integers doesn't do anything.

Contributes to CURA-329.
2016-02-03 12:52:55 +01:00
Ghostkeeper 71b466a933 Allow mirroring of infill/skin lines
To allow infill and skin lines to be traversed individually in reverse direction, the TSP solver now accepts an arbitrary number of orientations to insert an element. This also allows us to easily check for the actual start and end points of an element.

Contributes to CURA-329.
2016-02-03 12:52:52 +01:00
Ghostkeeper f0141230b2 Add test for basic bijective result
All points in the unoptimised set must be included in the optimised set and vice-versa.

Contributes to CURA-329.
2016-02-03 12:52:49 +01:00
Ghostkeeper d4631e3f69 Don't make stroke width 4
I must've accidentally included this in a previous commit. It was necessary to debug larger images properly with my image viewer.
2016-02-03 12:52:46 +01:00
Ghostkeeper 64aa1bcd97 Properly reverse list if starting point is at the end
If the optimal point to start is at the end of the TSP path, the TSP path is now properly reversed instead of just reversing the internal order of the elements.

Contributes to issue CURA-329.
2016-02-03 12:52:43 +01:00
Ghostkeeper d34e168562 Fix calculation of path closing distance
This distance was calculated using list.end(), which is a nullptr. This gave uninitialised data, and valgrind wasn't even able to detect it. Luckily, I was. It should be list.back().

Contributes to issue CURA-329.
2016-02-03 12:52:41 +01:00
Ghostkeeper 8f7afd75da Correct documentation
It is now inserting at the end rather than the start when best_insert is equal to result.end(), and inserting results in putting the element before the specified element.

Contributes to issue CURA-329.
2016-02-03 12:52:38 +01:00
Ghostkeeper 0380fd183d Set starting point depending on direction of elements
The direction of the elements is known at this point, so why not use it to optimise further.

Contributes to issue CURA-329.
2016-02-03 12:52:35 +01:00
Ghostkeeper c114904828 Fix checking for the last element inserting starting point
We were using --result.end(), but it turns out that this doesn't work in one statement. We have to create a variable and decrement that.

Contributes to issue CURA-329.
2016-02-03 12:52:32 +01:00
Ghostkeeper 9c08d3dd82 Properly average points
Arithmetic on points isn't defined, though the compiler doesn't complain. We'll have to do it manually.

Contributes to issue CURA-329.
2016-02-03 12:49:54 +01:00
Ghostkeeper f3dbde8e7f Properly iterate over cluster in reverse order
The index was off.

Contributes to issue CURA-329.
2016-02-03 12:49:51 +01:00
Ghostkeeper 7b0f4d204b Add method to write vector of polygons to SVG
This helps with debugging. I just want to be able to draw any geometry to SVG.
2016-02-03 12:49:48 +01:00
Ghostkeeper 79a2dc0a72 Only compute reverse elements if reverse is allowed
Otherwise we're just wasting computer cycles, man.

Contributes to issue CURA-329.
2016-02-03 12:49:45 +01:00
Ghostkeeper c97bcdf005 Order with average points and insert starting point last
This commit has two major changes for which a rewrite was required: The order of the pieces is now determined with point-based TSP rather than line-based. The average between the start and end point is used. This provides better results if the clusters are big, but worse results if the lines intersect (which is not our use case). Then to determine the direction of the elements, it is simply iterated through one by one after all elements are in the sequence.
Also, rather than inserting the starting point at the start, it is now inserted at the end. If it turns out that the starting point should've been inserted somewhere in the middle, that will be corrected after all elements are inserted.

Contributes to issue CURA-329.
2016-02-03 12:49:43 +01:00
Ghostkeeper 03677b8ac7 No longer sort line clusters
The clusters are now constructed with a nearest neighbour-ish search. This guarantees that the lines are already in order. No sorting necessary.

Contributes to issue CURA-329.
2016-02-03 12:49:40 +01:00
Ghostkeeper 9db6352281 Store polyStart during clustering
During clustering, the best orientation of the line is already decided in order to find the closest line. This orientation is immediately stored at that time, instead of guessing it later by the order of the lines at the serialisation. This should make the algorithm more robust at no additional computational cost.

Contributes to issue CURA-329.
2016-02-03 12:49:34 +01:00
Ghostkeeper 3da8607afd Remove old line order optimisation code
This code was kept commented out for a while to have a reference, but I won't need it any longer.

Contributes to issue CURA-329.
2016-02-03 12:49:31 +01:00
Ghostkeeper 4f248088ce Rewrite clustering code
The clustering now uses a nearest neighbour algorithm on the grid to generate the initial clusters to sort.

Contributes to issue CURA-329.
2016-02-03 12:49:28 +01:00
Ghostkeeper d08580f969 Terminate early if no lines to optimise
About 2/3rd of the times there are no lines to optimise for the LineOrderOptimizer. Instead of creating empty cluster vectors, intermediary vectors for reversing, optimising empty vectors with TSP, etc. it now just quits at the start of the optimiser.

Contributes to issue CURA-329.
2016-02-03 12:49:24 +01:00
Ghostkeeper a3652a4fc4 Communicate path order via polyStart properly
The polyStart field should contain an entry for every polygon in the same order as the input polygons, rather than the order of the output polygons.

Contributes to issue CURA-329.
2016-02-03 12:49:22 +01:00
Ghostkeeper 49426a0417 Visualise PolygonRef in SVG
This new method allows you to easily visualise PolygonRefs.
2016-02-03 12:49:17 +01:00
Ghostkeeper 01163bf581 Create constructor for AABB for PolygonRef and vector<PolygonRef>
This is so I won't have to unpack it in the code that uses the AABB.
2016-02-03 12:49:14 +01:00
Ghostkeeper b96bdf41b5 Use default comparator for std::pair
Turns out that the default comparator for std::pair already uses .first to compare two pairs, which is exactly what I had implemented.

Contributes to issue CURA-329.
2016-02-03 12:49:11 +01:00
Ghostkeeper 943c41ed7b Fix off-by-one error on reverse path lookup
When it serialises a path that should be reversed, there was an off-by one error. It now starts polygon_index by 1, which fixes the segfault.

Contributes to issue CURA-329.
2016-02-03 12:49:09 +01:00
Ghostkeeper a6cd192156 Fix off-by-one error on reverse path lookup
When it serialises a path that should be reversed, there was an off-by one error. It now starts polygon_index by 1, which fixes the segfault.

Contributes to issue CURA-329.
2016-02-03 12:49:06 +01:00
Ghostkeeper 06c82f3426 Factor out the cluster when serialising resulting optimised path
This makes the code a bit more maintainable.

Contributes to issue CURA-329.
2016-02-03 12:49:04 +01:00
Ghostkeeper eb06f19f11 Reverse sorting order within cluster
Since the lines were already almost sorted, this makes the sorting a lot faster.

Contributes to issue CURA-329.
2016-02-03 12:49:01 +01:00
Ghostkeeper 4fc471ecf9 Don't forget to use the sorted lines within a cluster
The lines were beautifully sorted by y-intercept as far as I know, but the sorted list was then discarded and the unsorted list was used. This fixes it.

Contributes to issue CURA-329.
2016-02-03 12:48:57 +01:00
Ghostkeeper ff018d40c0 Fix segfault
The construct of `bool picked[n] = {false}` gave a segfault for some reason, even though I just copied it from the internet where they used the same construct with an int array. Maybe this doesn't work properly for booleans with this construct in GCC.

Contributes to issue CURA-329.
2016-02-03 12:48:54 +01:00
Ghostkeeper cc9199ee50 Use clustering to make path optimiser more efficient
Groups of lines where both endpoints are close together are clustered. Within a cluster, the lines are sorted in order of increasing y-intercept, which should work excellently for infill and skin lines. The clusters are then optimised with TSP. Not properly tested yet since elsewhere in the code it has a segfault, but I wanted to make these separate commits.

Contributes to issue CURA-329.
2016-02-03 12:48:48 +01:00
Ghostkeeper 7bcef0ad97 Prettier array initialisation
Should've looked this up first. I keep forgetting this syntactic sugar.

Contributes to issue CURA-329.
2016-02-03 12:48:45 +01:00
Ghostkeeper f5f9595627 Write a clustering method for infill lines
This will cluster lines of infill together so that they don't need to be ordered all individually. The clustering method is fairly dumb right now. It could be made much more effective with some smarter data structures.

Contributes to issue CURA-329.
2016-02-03 12:48:42 +01:00
Ghostkeeper d2f2417012 Optimise indices of size_t instead of ints
Contributes to issue CURA-329.
2016-02-03 12:48:39 +01:00
Ghostkeeper 5299c76521 Document the calling of TSP.
Contributes to issue CURA-329.
2016-02-03 12:48:36 +01:00
Ghostkeeper 80f976d1f3 Remove duplicate code for reverse paths
In the previous commit I immediately finalised reverse paths in their waypoints as soon as it was known that the path was to be reversed. This changed the code for the non-reversed try (when reverse paths are allowed) more or less equal to the code for when reversing paths isn't allowed. I removed the code for this non-reversed try, making it always try non-reversed and only try reversed if reversed is allowed.

Contributes to issue CURA-329.
2016-02-03 12:48:33 +01:00
Ghostkeeper b117e64288 Store reverse path into waypoint
This makes the algorithm run a bit faster and makes the code simpler. Instead of relying only on the is_reversed field and checking that field every time we need to look up the endpoints of the element, we now simply swap the endpoints if the element needs to be reversed.

Contributes to issue CURA-329.
2016-02-03 12:48:30 +01:00
Ghostkeeper ca41665c3e std::list.insert() inserts before an item, not after
I understood this wrong from the documentation. This makes several things easier. This could be optimised slightly but that is of later concern.

Contributes to issue CURA-329.
2016-02-03 12:48:25 +01:00
Ghostkeeper b94c95149a Use vSize rather than a custom float-implementation
This one is a bit slower but it is neater to re-use code, and we are more certain that it works.

Contributes to issue CURA-329.
2016-02-03 12:48:22 +01:00
Ghostkeeper 5b95b7a557 Properly call TravellingSalesman from optimiser
It needs to optimise the order of indices to PolygonRefs at the moment. I'd like to refactor this construction away... But that takes some effort.

Contributes to issue CURA-329.
2016-02-03 12:48:20 +01:00
Ghostkeeper bdc31425ba Skip the starting waypoint
If there is a starting point, it produces a waypoint somewhere in the path (either the first or the last point). This waypoint shouldn't be included in the eventual path. Skip it. This can be done a bit more efficient but that is of later concern.

Contributes to issue CURA-329.
2016-02-03 12:48:17 +01:00
Ghostkeeper f001f31e78 TSP pass by value instead of by reference
This prevents segfaults when the underlying vectors are resized for any reason.
2016-02-03 12:48:14 +01:00
Ghostkeeper 6d56e2c622 Actually add waypoints to shuffle vector
It seems I had forgotten to add waypoints to be shuffled.

Contributes to issue CURA-329.
2016-02-03 12:48:11 +01:00
Ghostkeeper c983a7e83b TSP prepends new path before first element
Instead of trying to prepend new elements before the original first element, it now prepends new elements before the currently first element.
2016-02-03 12:48:08 +01:00
Ghostkeeper 86d0ce4e6b Refactor the TSP solver
The algorithm is no longer implemented twice, but once with a load of ifs checking for allow_reverse. I'm still not sure which is neater...
2016-02-03 12:48:05 +01:00
Ghostkeeper f79933b140 Implement findPath
This version tries to insert elements also in reverse direction, which has some effects on various parts of the code. It also turned out that the previous version wasn't compiling properly but that didn't come up because the function was never used, so it was being optimised out.
2016-02-03 12:45:44 +01:00
Ghostkeeper a47716161a Document the linearising of the result
This wasn't quite clear in a re-read of my code.
2016-02-03 12:45:41 +01:00
Ghostkeeper 831f5ea16e Refactor TSP to use templates
Kind of a big rewrite and not properly tested yet.
2016-02-03 12:45:37 +01:00
Ghostkeeper 219d400e62 Correct license header
Yeah that was wrong.
2016-02-03 12:45:34 +01:00
Ghostkeeper 2b8c92cba9 Use TSP solver in path order optimiser
It also prints out the optimised path now, though that is not what we'd like to see eventually.
2016-02-03 12:45:30 +01:00
Ghostkeeper 44c5c11160 Fix memory pre-allocation
Turns out that it doesn't work to put the required memory in the constructor. You have to call .reserve(int) separately.
2016-02-03 12:45:26 +01:00
Ghostkeeper 1f5cdf5d6b Add TSP for line segments
This implementation is for TSP that visits a selection of line segments, following each line segment in the order that makes the total path short. Line segments may also be traversed in reverse direction.
2016-02-03 12:45:23 +01:00
Ghostkeeper 7607992eb6 Fix start point check
The check to never insert before the start point was wrong. It should've inserted before only if the selected point is not the starting point or there is no starting point.
2016-02-03 12:45:19 +01:00
Ghostkeeper 2461073d64 Add new waypoints to bucket grid
This would allow them to be found quickly, or rather, to be found at all when looking for nearby points.
2016-02-03 12:45:15 +01:00
Ghostkeeper 3a43fe2885 Delete waypoints after use
This prevents a memory leak.
2016-02-03 12:45:10 +01:00
Ghostkeeper 9e2d5217d0 Add return documentation
Forgot to add that previously.
2016-02-03 12:45:07 +01:00
Ghostkeeper 353e151c6b Initial TSP implementation
This implementation should work for TSP with points. TSP with lines coming up next. Has not yet been tested thoroughly, but the code is not used anywhere.
2016-02-03 12:45:01 +01:00
142 arquivos alterados com 1931004 adições e 9566 exclusões
-15
Ver Arquivo
@@ -11,26 +11,11 @@ NUL
build/*
*.pyc
*.exe
*.a
*.o
CuraEngine
_bin
_obj
## CMake files
cmake_install.cmake
CMakeCache.txt
CMakeFiles/
CPackSourceConfig.cmake
# Visual Studio files generated by CMake
*.vcxproj
*.vcxproj.filters
CuraEngine.sln
# Makefile generated by CMake
Makefile
## IDE project files.
CuraEngine.layout
CuraEngine.cbp
+12 -58
Ver Arquivo
@@ -2,14 +2,7 @@ project(CuraEngine)
cmake_minimum_required(VERSION 2.8.12)
option (ENABLE_ARCUS
"Enable support for ARCUS" ON)
if (ENABLE_ARCUS)
message(STATUS "Building with Arcus")
find_package(Arcus REQUIRED)
add_definitions(-DARCUS)
endif ()
find_package(Arcus REQUIRED)
if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1)
set(CMAKE_CXX_STANDARD 11)
@@ -29,8 +22,8 @@ option(BUILD_TESTS OFF)
# Add a compiler flag to check the output for insane values if we are in debug mode.
if(CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Building debug release of CuraEngine.")
add_definitions(-DASSERT_INSANE_OUTPUT)
message(STATUS "Building debug release of CuraEngine.")
add_definitions(-DASSERT_INSANE_OUTPUT)
endif()
# Add warnings
@@ -46,16 +39,15 @@ add_library(clipper STATIC libs/clipper/clipper.cpp)
set(engine_SRCS # Except main.cpp.
src/bridge.cpp
src/comb.cpp
src/commandSocket.cpp
src/ExtruderTrain.cpp
src/FffGcodeWriter.cpp
src/FffPolygonGenerator.cpp
src/FffProcessor.cpp
src/gcodeExport.cpp
src/GCodePathConfig.cpp
src/gcodePlanner.cpp
src/infill.cpp
src/WallsComputation.cpp
src/inset.cpp
src/layerPart.cpp
src/LayerPlanBuffer.cpp
src/MergeInfillLines.cpp
@@ -64,14 +56,16 @@ set(engine_SRCS # Except main.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/WallsComputation.cpp
src/wallOverlap.cpp
src/Weaver.cpp
src/Wireframe2gcode.cpp
@@ -82,22 +76,7 @@ set(engine_SRCS # Except main.cpp.
src/infill/ZigzagConnectorProcessorEndPieces.cpp
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
src/pathPlanning/Comb.cpp
src/pathPlanning/LinePolygonsCrossings.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
src/utils/polygonUtils.cpp
src/utils/polygon.cpp
@@ -105,27 +84,17 @@ set(engine_SRCS # Except main.cpp.
# List of tests. For each test there must be a file tests/${NAME}.cpp and a file tests/${NAME}.h.
set(engine_TEST
GCodePlannerTest
)
set(engine_TEST_INFILL
)
set(engine_TEST_UTILS
BucketGrid2DTest
GCodePlannerTest
LinearAlg2DTest
PolygonUtilsTest
TravellingSalesmanTest
)
# Generating ProtoBuf protocol
if (ENABLE_ARCUS)
# Generating ProtoBuf protocol.
protobuf_generate_cpp(engine_PB_SRCS engine_PB_HEADERS Cura.proto)
endif ()
# Compiling CuraEngine itself.
add_library(_CuraEngine ${engine_SRCS} ${engine_PB_SRCS}) #First compile all of CuraEngine as library, allowing this to be re-used for tests.
target_link_libraries(_CuraEngine clipper)
if (ENABLE_ARCUS)
target_link_libraries(_CuraEngine Arcus)
endif ()
target_link_libraries(_CuraEngine clipper Arcus)
set_target_properties(_CuraEngine PROPERTIES COMPILE_DEFINITIONS "VERSION=\"${CURA_ENGINE_VERSION}\"")
@@ -144,23 +113,8 @@ if (BUILD_TESTS)
target_link_libraries(${test} _CuraEngine cppunit)
add_test(${test} ${test})
endforeach()
foreach (test ${engine_TEST_INFILL})
add_executable(${test} tests/main.cpp tests/infill/${test}.cpp)
target_link_libraries(${test} _CuraEngine cppunit)
add_test(${test} ${test})
endforeach()
foreach (test ${engine_TEST_UTILS})
add_executable(${test} tests/main.cpp tests/utils/${test}.cpp)
target_link_libraries(${test} _CuraEngine cppunit)
add_test(${test} ${test})
endforeach()
endif()
add_custom_command(TARGET CuraEngine POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/resources $<TARGET_FILE_DIR:CuraEngine>)
# Installing CuraEngine.
include(GNUInstallDirs)
install(TARGETS CuraEngine DESTINATION ${CMAKE_INSTALL_BINDIR})
+40 -30
Ver Arquivo
@@ -2,26 +2,20 @@ syntax = "proto3";
package cura.proto;
message ObjectList
message ObjectList
{
repeated Object objects = 1;
repeated Setting settings = 2; // meshgroup settings (for one-at-a-time printing)
repeated Setting settings = 2;
}
// typeid 1
message Slice
{
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
SettingList global_settings = 2; // The global settings used for the whole print job
repeated Extruder extruders = 3; // The settings sent to each extruder object
repeated ObjectList object_lists = 1;
}
message Extruder
{
int32 id = 1;
SettingList settings = 2;
}
message Object
message Object
{
int64 id = 1;
bytes vertices = 2; //An array of 3 floats.
@@ -30,17 +24,32 @@ message Object
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
}
message Progress
// typeid 3
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; // Z position
float thickness = 3; // height of a single layer
repeated Polygon polygons = 4; // layer data
float height = 2;
float thickness = 3;
repeated Polygon polygons = 4;
}
message Polygon {
@@ -56,39 +65,40 @@ message Polygon {
MoveCombingType = 8;
MoveRetractionType = 9;
}
Type type = 1; // Type of move
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
float line_width = 3; // The width of the line being laid down
Type type = 1;
bytes points = 2;
float line_width = 3;
}
// typeid 4
message GCodeLayer {
int64 id = 1;
bytes data = 2;
}
message PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for each extruder
float time = 1; // Total time estimate
repeated MaterialEstimates materialEstimates = 2; // materialEstimates data
}
message MaterialEstimates {
// typeid 5
message ObjectPrintTime {
int64 id = 1;
float material_amount = 2; // material used in the extruder
float time = 2;
float material_amount = 3;
}
// typeid 6
message SettingList {
repeated Setting settings = 1;
}
message Setting {
string name = 1; // Internal key to signify a setting
string name = 1;
bytes value = 2; // The value of the setting
bytes value = 2;
}
// typeid 7
message GCodePrefix {
bytes data = 2; // Header string to be prenpended before the rest of the gcode sent from the engine
bytes data = 2;
}
// typeid 8
message SlicingFinished {
}
+1 -1
Ver Arquivo
@@ -178,7 +178,7 @@ JAVADOC_AUTOBRIEF = NO
# requiring an explicit \brief command for a brief description.)
# The default value is: NO.
QT_AUTOBRIEF = YES
QT_AUTOBRIEF = NO
# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+1 -8
Ver Arquivo
@@ -51,21 +51,14 @@ Running
=======
Other than running CuraEngine from a frontend, such as Ultimaker/Cura, one can run CuraEngine from the command line.
For that one needs a settings JSON file, which can be found in the Ultimaker/Cura repository.
Note that the structure of the json files has changed since 2.1. In the corresponding branch of the Cura repository you can find how the json files used to be structured.
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/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"
./build/CuraEngine slice -v -j ../Cura/resources/machines/dual_extrusion_printer.json -o "output/test.gcode" -e1 -s infill_line_distance=0 -e0 -l "/model_1.stl" -e1 -l "fully_filled_model.stl"
```
Run `CuraEngine help` for a general description of how to use the CuraEngine tool.
[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
=========
+3 -34
Ver Arquivo
@@ -1,39 +1,8 @@
=====================================================================
Clipper Change Log
=====================================================================
v6.2.1 (31 October 2014) Rev 482
* Bugfix in ClipperOffset.Execute where the Polytree.IsHole property
was returning incorrect values with negative offsets
* Very minor improvement to join rounding in ClipperOffset
* Fixed CPP OpenGL demo.
v6.2.0 (17 October 2014) Rev 477
* Numerous minor bugfixes, too many to list.
(See revisions 454-475 in Sourceforge Repository)
* The ZFillFunction (custom callback function) has had its parameters
changed.
* Curves demo removed (temporarily).
* Deprecated functions have been removed.
v6.1.5 (26 February 2014) Rev 460
* Improved the joining of output polygons sharing a common edge
when those common edges are horizontal.
* Fixed a bug in ClipperOffset.AddPath() which would produce
incorrect solutions when open paths were added before closed paths.
* Minor code tidy and performance improvement
v6.1.4 (6 February 2014)
* Fixed bugs in MinkowskiSum
* Fixed minor bug when using Clipper.ForceSimplify.
* Modified use_xyz callback so that all 4 vertices around an
intersection point are now passed to the callback function.
v6.1.3a (22 January 2014) Rev 453
* Fixed buggy PointInPolygon function (C++ and C# only).
Note this bug only affected the newly exported function, the
internal PointInPolygon function used by Clipper was OK.
v6.1.3 (19 January 2014) Rev 452
v6.1.3 (19 January 2014)
* Fixed potential endless loop condition when adding open
paths to Clipper.
* Fixed missing implementation of SimplifyPolygon function
@@ -44,11 +13,11 @@ v6.1.3 (19 January 2014) Rev 452
* Overloaded MinkowskiSum function to accommodate multi-contour
paths.
v6.1.2 (15 December 2013) Rev 444
v6.1.2 (15 December 2013)
* Fixed broken C++ header file.
* Minor improvement to joining polygons.
v6.1.1 (13 December 2013) Rev 441
v6.1.1 (13 December 2013)
* Fixed a couple of bugs affecting open paths that could
raise unhandled exceptions.
+535 -389
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+36 -33
Ver Arquivo
@@ -1,8 +1,8 @@
/*******************************************************************************
* *
* Author : Angus Johnson *
* Version : 6.2.1 *
* Date : 31 October 2014 *
* Version : 6.1.3a *
* Date : 22 January 2014 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2014 *
* *
@@ -34,7 +34,7 @@
#ifndef clipper_hpp
#define clipper_hpp
#define CLIPPER_VERSION "6.2.0"
#define CLIPPER_VERSION "6.1.3"
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
//improve performance but coordinate values are limited to the range +/- 46340
@@ -44,10 +44,11 @@
//#define use_xyz
//use_lines: Enables line clipping. Adds a very minor cost to performance.
#define use_lines
//#define use_lines
//use_deprecated: Enables temporary support for the obsolete functions
//#define use_deprecated
//use_deprecated: Enables support for the obsolete OffsetPaths() function
//which has been replace with the ClipperOffset class.
#define use_deprecated
#include <vector>
#include <set>
@@ -56,7 +57,6 @@
#include <cstdlib>
#include <ostream>
#include <functional>
#include <queue>
namespace ClipperLib {
@@ -69,16 +69,11 @@ enum PolyType { ptSubject, ptClip };
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
#ifdef use_int32
typedef int cInt;
static cInt const loRange = 0x7FFF;
static cInt const hiRange = 0x7FFF;
typedef int cInt;
typedef unsigned int cUInt;
#else
typedef signed long long cInt;
static cInt const loRange = 0x3FFFFFFF;
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
typedef signed long long long64; //used by Int128 class
typedef unsigned long long ulong64;
typedef signed long long cInt;
typedef unsigned long long cUInt;
#endif
struct IntPoint {
@@ -122,12 +117,15 @@ struct DoublePoint
//------------------------------------------------------------------------------
#ifdef use_xyz
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt);
#endif
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
enum JoinType {jtSquare, jtRound, jtMiter};
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
#ifdef use_deprecated
enum EndType_ {etClosed, etButt = 2, etSquare, etRound};
#endif
class PolyNode;
typedef std::vector< PolyNode* > PolyNodes;
@@ -136,7 +134,6 @@ class PolyNode
{
public:
PolyNode();
virtual ~PolyNode(){};
Path Contour;
PolyNodes Childs;
PolyNode* Parent;
@@ -171,6 +168,11 @@ bool Orientation(const Path &poly);
double Area(const Path &poly);
int PointInPolygon(const IntPoint &pt, const Path &path);
#ifdef use_deprecated
void OffsetPaths(const Paths &in_polys, Paths &out_polys,
double delta, JoinType jointype, EndType_ endtype, double limit = 0);
#endif
void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
@@ -181,7 +183,8 @@ void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.
void CleanPolygons(Paths& polys, double distance = 1.415);
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
void MinkowskiSum(const Path& pattern, const Paths& paths,
Paths& solution, PolyFillType pathFillType, bool pathIsClosed);
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
@@ -199,7 +202,7 @@ enum EdgeSide { esLeft = 1, esRight = 2};
//forward declarations (for stuff used internally) ...
struct TEdge;
struct IntersectNode;
struct LocalMinimum;
struct LocalMinima;
struct Scanbeam;
struct OutPt;
struct OutRec;
@@ -210,6 +213,7 @@ typedef std::vector < TEdge* > EdgeList;
typedef std::vector < Join* > JoinList;
typedef std::vector < IntersectNode* > IntersectList;
//------------------------------------------------------------------------------
//ClipperBase is the ancestor to the Clipper class. It should not be
@@ -232,14 +236,12 @@ protected:
void PopLocalMinima();
virtual void Reset();
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
void InsertLocalMinima(LocalMinima *newLm);
void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed);
TEdge* DescendToMin(TEdge *&E);
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
typedef std::vector<LocalMinimum> MinimaList;
MinimaList::iterator m_CurrentLM;
MinimaList m_MinimaList;
LocalMinima *m_CurrentLM;
LocalMinima *m_MinimaList;
bool m_UseFullRange;
EdgeList m_edges;
bool m_PreserveCollinear;
@@ -266,7 +268,7 @@ public:
void StrictlySimple(bool value) {m_StrictSimple = value;};
//set the callback function for z value filling on intersections (otherwise Z is 0)
#ifdef use_xyz
void ZFillFunction(ZFillCallback zFillFunc);
void ZFillFunction(TZFillCallback zFillFunc);
#endif
protected:
void Reset();
@@ -277,8 +279,7 @@ private:
JoinList m_GhostJoins;
IntersectList m_IntersectList;
ClipType m_ClipType;
typedef std::priority_queue<cInt> ScanbeamList;
ScanbeamList m_Scanbeam;
std::set< cInt, std::greater<cInt> > m_Scanbeam;
TEdge *m_ActiveEdges;
TEdge *m_SortedEdges;
bool m_ExecuteLocked;
@@ -288,7 +289,7 @@ private:
bool m_UsingPolyTree;
bool m_StrictSimple;
#ifdef use_xyz
ZFillCallback m_ZFill; //custom callback
TZFillCallback m_ZFill; //custom callback
#endif
void SetWindingCount(TEdge& edge);
bool IsEvenOddFillType(const TEdge& edge) const;
@@ -307,19 +308,21 @@ private:
bool IsTopHorz(const cInt XPos);
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
void DoMaxima(TEdge *e);
void PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam);
void ProcessHorizontals(bool IsTopOfScanbeam);
void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam);
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutRec* GetOutRec(int idx);
void AppendPolygon(TEdge *e1, TEdge *e2);
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
void IntersectEdges(TEdge *e1, TEdge *e2,
const IntPoint &pt, bool protect = false);
OutRec* CreateOutRec();
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
void DisposeAllOutRecs();
void DisposeOutRec(PolyOutList::size_type index);
bool ProcessIntersections(const cInt topY);
void BuildIntersectList(const cInt topY);
bool ProcessIntersections(const cInt botY, const cInt topY);
void BuildIntersectList(const cInt botY, const cInt topY);
void ProcessIntersectList();
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
void BuildResult(Paths& polys);
@@ -341,7 +344,7 @@ private:
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
#ifdef use_xyz
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
void SetZ(IntPoint& pt, TEdge& e);
#endif
};
//------------------------------------------------------------------------------
-19
Ver Arquivo
@@ -1,19 +0,0 @@
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.

Antes

Largura:  |  Altura:  |  Tamanho: 284 KiB

-42
Ver Arquivo
@@ -1,42 +0,0 @@
{
"version": 1,
"name": "Command line setting defaults CuraEngine",
"author": "Ultimaker B.V.",
"categories": {
"command_line_settings": {
"label": "Command Line Settings",
"settings": {
"center_object": {
"description": "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved.",
"type": "boolean",
"default": false
},
"machine_print_temp_wait": {
"description": "Whether to wait for the nozzle temperature to be reached when preheating the nozzles at the start of the gcode.",
"type": "boolean",
"default": true
},
"mesh_position_x": {
"description": "Offset applied to the object in the x direction.",
"type": "float",
"default": 0
},
"mesh_position_y": {
"description": "Offset applied to the object in the y direction.",
"type": "float",
"default": 0
},
"mesh_position_z": {
"description": "Offset applied to the object in the z direction. With this you can perform what was used to call 'Object Sink'.",
"type": "float",
"default": 0
},
"prime_tower_dir_outward": {
"description": "Whether to start printing in the middle of the prime tower and end up at the perimeter, or the other way around. This is only used for certain types of prime tower.",
"type": "boolean",
"default": false
}
}
}
}
}
-16
Ver Arquivo
@@ -1,16 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "ExtruderTrain.h"
namespace cura
{
int ExtruderTrain::getExtruderNr()
{
return extruder_nr;
}
ExtruderTrain::ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
: SettingsBase(settings)
, extruder_nr(extruder_nr)
{
}
}//namespace cura
+8 -6
Ver Arquivo
@@ -1,8 +1,7 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef EXTRUDER_TRAIN_H
#define EXTRUDER_TRAIN_H
#include "settings/settings.h"
#include "settings.h"
namespace cura
{
@@ -11,10 +10,13 @@ class ExtruderTrain : public SettingsBase
{
int extruder_nr;
public:
int getExtruderNr();
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr);
int getExtruderNr() { return extruder_nr; }
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
: SettingsBase(settings)
, extruder_nr(extruder_nr)
{ }
};
}//namespace cura
+1 -1
Ver Arquivo
@@ -1,7 +1,7 @@
#ifndef FAN_SPEED_LAYER_TIME_H
#define FAN_SPEED_LAYER_TIME_H
#include "settings/settings.h"
#include "settings.h"
namespace cura
{
+200 -289
Ver Arquivo
@@ -3,7 +3,7 @@
#include "FffGcodeWriter.h"
#include "FffProcessor.h"
#include "progress/Progress.h"
#include "Progress.h"
#include "wallOverlap.h"
namespace cura
@@ -12,28 +12,17 @@ namespace cura
void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keeper)
{
PrimeTower primetower();
gcode.preSetup(storage.meshgroup);
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
{ // first meshgroup
if (meshgroup_number == 1)
{
gcode.resetTotalPrintTimeAndFilament();
gcode.setInitialTemps(*storage.meshgroup);
}
// set the initial extruder of this meshgroup
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
{ // first meshgroup
current_extruder_planned = getSettingAsIndex("adhesion_extruder_nr");
}
else
{
current_extruder_planned = gcode.getExtruderNr();
}
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->beginGCode();
}
setConfigFanSpeedLayerTime();
@@ -51,7 +40,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
layer_plan_buffer.setPreheatConfig(*storage.meshgroup);
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
if (meshgroup_number == 1)
{
processStartingCode(storage);
}
@@ -59,6 +48,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
{
processNextMeshGroupCode(storage);
}
meshgroup_number++;
size_t total_layers = 0;
for (SliceMeshStorage& mesh : storage.meshes)
@@ -116,57 +106,68 @@ 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"))? train->getSettingInMillimeters("retraction_amount") : 0;
retraction_config.distance = (train->getSettingBoolean("retraction_enable"))? INT2MM(train->getSettingInMicrons("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 = train->getSettingInMillimeters("retraction_extrusion_window");
retraction_config.retraction_extrusion_window = INT2MM(train->getSettingInMicrons("retraction_extrusion_window"));
retraction_config.retraction_count_max = train->getSettingAsCount("retraction_count_max");
RetractionConfig& switch_retraction_config = storage.extruder_switch_retraction_config_per_extruder[extruder];
switch_retraction_config.distance = train->getSettingInMillimeters("switch_extruder_retraction_amount");
switch_retraction_config.prime_volume = 0.0;
switch_retraction_config.speed = train->getSettingInMillimetersPerSecond("switch_extruder_retraction_speed");
switch_retraction_config.primeSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_prime_speed");
switch_retraction_config.zHop = retraction_config.zHop; // not used, because the last_retraction_config is used to govern how how high to zHop
switch_retraction_config.retraction_min_travel_distance = 0; // no limitation on travel distance for an extruder switch retract
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)
switch_retraction_config.retraction_count_max = 9999999; // extruder switch retraction is never limited
}
for(SliceMeshStorage& mesh : storage.meshes)
{
mesh.retraction_config.distance = (mesh.getSettingBoolean("retraction_enable"))? INT2MM(mesh.getSettingInMicrons("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_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->getSettingInMillimetersPerSecond("acceleration_skirt"), train->getSettingInMillimetersPerSecond("jerk_skirt"), train->getSettingInMicrons("skirt_line_width"), train->getSettingInPercentage("material_flow"));
storage.travel_config_per_extruder[extruder].init(train->getSettingInMillimetersPerSecond("speed_travel"), train->getSettingInMillimetersPerSecond("acceleration_travel"), train->getSettingInMillimetersPerSecond("jerk_travel"), 0, 0);
storage.skirt_config[extruder].init(train->getSettingInMillimetersPerSecond("skirt_speed"), train->getSettingInMicrons("skirt_line_width"), train->getSettingInPercentage("material_flow"));
}
{ // support
SettingsBase* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_infill_extruder_nr"));
storage.support_config.init(getSettingInMillimetersPerSecond("speed_support_infill"), getSettingInMillimetersPerSecond("acceleration_support_infill"), getSettingInMillimetersPerSecond("jerk_support_infill"), getSettingInMicrons("support_line_width"), train->getSettingInPercentage("material_flow"));
SettingsBase* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_extruder_nr"));
storage.support_config.init(getSettingInMillimetersPerSecond("speed_support_lines"), getSettingInMicrons("support_line_width"), train->getSettingInPercentage("material_flow"));
storage.support_roof_config.init(getSettingInMillimetersPerSecond("speed_support_roof"), getSettingInMillimetersPerSecond("acceleration_support_roof"), getSettingInMillimetersPerSecond("jerk_support_roof"), getSettingInMicrons("support_roof_line_width"), train->getSettingInPercentage("material_flow"));
storage.support_roof_config.init(getSettingInMillimetersPerSecond("speed_support_roof"), getSettingInMicrons("support_roof_line_width"), train->getSettingInPercentage("material_flow"));
}
for (SliceMeshStorage& mesh : storage.meshes)
{
mesh.inset0_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_0"), mesh.getSettingInMillimetersPerSecond("acceleration_wall_0"), mesh.getSettingInMillimetersPerSecond("jerk_wall_0"), mesh.getSettingInMicrons("wall_line_width_0"), mesh.getSettingInPercentage("material_flow"));
mesh.insetX_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_x"), mesh.getSettingInMillimetersPerSecond("acceleration_wall_x"), mesh.getSettingInMillimetersPerSecond("jerk_wall_x"), mesh.getSettingInMicrons("wall_line_width_x"), mesh.getSettingInPercentage("material_flow"));
mesh.skin_config.init(mesh.getSettingInMillimetersPerSecond("speed_topbottom"), mesh.getSettingInMillimetersPerSecond("acceleration_topbottom"), mesh.getSettingInMillimetersPerSecond("jerk_topbottom"), mesh.getSettingInMicrons("skin_line_width"), mesh.getSettingInPercentage("material_flow"));
mesh.inset0_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_0"), mesh.getSettingInMicrons("wall_line_width_0"), mesh.getSettingInPercentage("material_flow"));
mesh.insetX_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_x"), mesh.getSettingInMicrons("wall_line_width_x"), mesh.getSettingInPercentage("material_flow"));
mesh.skin_config.init(mesh.getSettingInMillimetersPerSecond("speed_topbottom"), mesh.getSettingInMicrons("skin_line_width"), mesh.getSettingInPercentage("material_flow"));
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
{
mesh.infill_config[idx].init(mesh.getSettingInMillimetersPerSecond("speed_infill"), mesh.getSettingInMillimetersPerSecond("acceleration_infill"), mesh.getSettingInMillimetersPerSecond("jerk_infill"), mesh.getSettingInMicrons("infill_line_width") * (idx + 1), mesh.getSettingInPercentage("material_flow"));
mesh.infill_config[idx].init(mesh.getSettingInMillimetersPerSecond("speed_infill"), mesh.getSettingInMicrons("infill_line_width") * (idx + 1), mesh.getSettingInPercentage("material_flow"));
}
}
@@ -177,15 +178,17 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
{
if (!CommandSocket::isInstantiated())
{
std::string prefix = gcode.getFileHeader();
gcode.writeCode(prefix.c_str());
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");
}
}
int start_extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
if (gcode.getFlavor() != EGCodeFlavor::ULTIGCODE && gcode.getFlavor() != EGCodeFlavor::GRIFFIN)
if (gcode.getFlavor() != EGCodeFlavor::ULTIGCODE)
{
if (getSettingBoolean("material_bed_temp_prepend"))
{
@@ -197,24 +200,29 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
if (getSettingBoolean("material_print_temp_prepend"))
{
for (int extruder_nr = 0; extruder_nr < storage.getSettingAsCount("machine_extruder_count"); extruder_nr++)
for(SliceMeshStorage& mesh : storage.meshes)
{
double print_temp = storage.meshgroup->getExtruderTrain(extruder_nr)->getSettingInDegreeCelsius("material_print_temperature");
gcode.writeTemperatureCommand(extruder_nr, print_temp);
if (mesh.getSettingInDegreeCelsius("material_print_temperature") > 0)
{
gcode.writeTemperatureCommand(mesh.getSettingAsIndex("extruder_nr"), mesh.getSettingInDegreeCelsius("material_print_temperature"));
}
}
if (getSettingBoolean("material_print_temp_wait"))
{
for (int extruder_nr = 0; extruder_nr < storage.getSettingAsCount("machine_extruder_count"); extruder_nr++)
for(SliceMeshStorage& mesh : storage.meshes)
{
double print_temp = storage.meshgroup->getExtruderTrain(extruder_nr)->getSettingInDegreeCelsius("material_print_temperature");
gcode.writeTemperatureCommand(extruder_nr, print_temp, true);
if (mesh.getSettingInDegreeCelsius("material_print_temperature") > 0)
{
gcode.writeTemperatureCommand(mesh.getSettingAsIndex("extruder_nr"), mesh.getSettingInDegreeCelsius("material_print_temperature"), true);
}
}
}
}
}
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
if (gcode.getFlavor() == EGCodeFlavor::BFB)
{
gcode.writeComment("enable auto-retraction");
@@ -222,15 +230,6 @@ 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.startExtruder(start_extruder_nr);
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(start_extruder_nr);
constexpr bool wait = true;
gcode.writeTemperatureCommand(start_extruder_nr, train.getSettingInDegreeCelsius("material_print_temperature"), wait);
gcode.writePrimeTrain(train.getSettingInMillimetersPerSecond("speed_travel"));
}
}
void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
@@ -238,9 +237,8 @@ void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
gcode.writeFanCommand(0);
gcode.resetExtrusionValue();
gcode.setZ(max_object_height + 5000);
gcode.writeMove(gcode.getPositionXY(), storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->getSettingInMillimetersPerSecond("speed_travel"), 0);
last_position_planned = Point(storage.model_min.x, storage.model_min.y);
gcode.writeMove(last_position_planned, storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->getSettingInMillimetersPerSecond("speed_travel"), 0);
gcode.writeMove(gcode.getPositionXY(), getSettingInMillimetersPerSecond("speed_travel"), 0);
gcode.writeMove(Point(storage.model_min.x, storage.model_min.y), getSettingInMillimetersPerSecond("speed_travel"), 0);
}
void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_layers)
@@ -248,40 +246,40 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
int extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder_nr);
CombingMode combing_mode = storage.getSettingAsCombingMode("retraction_combing");
bool retraction_combing = true;
int n_raft_surface_layers = train->getSettingAsCount("raft_surface_layers");
int z = 0;
{ // set configs
storage.raft_base_config.init(train->getSettingInMillimetersPerSecond("raft_base_speed"), train->getSettingInMillimetersPerSecond("raft_base_acceleration"), train->getSettingInMillimetersPerSecond("raft_base_jerk"), train->getSettingInMicrons("raft_base_line_width"), train->getSettingInPercentage("material_flow"));
storage.raft_base_config.init(train->getSettingInMillimetersPerSecond("raft_base_speed"), train->getSettingInMicrons("raft_base_line_width"), train->getSettingInPercentage("material_flow"));
storage.raft_base_config.setLayerHeight(train->getSettingInMicrons("raft_base_thickness"));
storage.raft_interface_config.init(train->getSettingInMillimetersPerSecond("raft_interface_speed"), train->getSettingInMillimetersPerSecond("raft_interface_acceleration"), train->getSettingInMillimetersPerSecond("raft_interface_jerk"), train->getSettingInMicrons("raft_interface_line_width"), train->getSettingInPercentage("material_flow"));
storage.raft_interface_config.init(train->getSettingInMillimetersPerSecond("raft_interface_speed"), train->getSettingInMicrons("raft_interface_line_width"), train->getSettingInPercentage("material_flow"));
storage.raft_interface_config.setLayerHeight(train->getSettingInMicrons("raft_interface_thickness"));
storage.raft_surface_config.init(train->getSettingInMillimetersPerSecond("raft_surface_speed"), train->getSettingInMillimetersPerSecond("raft_surface_acceleration"), train->getSettingInMillimetersPerSecond("raft_surface_jerk"), train->getSettingInMicrons("raft_surface_line_width"), train->getSettingInPercentage("material_flow"));
storage.raft_surface_config.init(train->getSettingInMillimetersPerSecond("raft_surface_speed"), train->getSettingInMicrons("raft_surface_line_width"), train->getSettingInPercentage("material_flow"));
storage.raft_surface_config.setLayerHeight(train->getSettingInMicrons("raft_surface_thickness"));
}
// some infill config for all lines infill generation below
Polygons* in_between = nullptr;
int offset_from_poly_outline = 0;
bool avoidOverlappingPerimeters = false;
double fill_overlap = 0; // raft line shouldn't be expanded - there is no boundary polygon printed
Polygons raft_polygons; // should remain empty, since we only have the lines pattern for the raft...
{ // raft base layer
int layer_nr = -n_raft_surface_layers - 2;
int layer_height = train->getSettingInMicrons("raft_base_thickness");
int layer_height = getSettingInMicrons("raft_base_thickness");
z += layer_height;
int64_t comb_offset = train->getSettingInMicrons("raft_base_line_spacing");
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings, combing_mode, 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, fan_speed_layer_time_settings, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
gcode_layer.setIsInside(false);
if (getSettingAsIndex("adhesion_extruder_nr") > 0)
{
gcode_layer.setExtruder(extruder_nr);
}
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
@@ -290,13 +288,12 @@ 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, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle);
infill_comp.generate(raft_polygons, raftLines);
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle);
infill_comp.generate(raft_polygons, raftLines, in_between);
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_base_config, SpaceFillType::Lines);
last_position_planned = gcode_layer.getLastPosition();
current_extruder_planned = gcode_layer.getExtruder();
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
gcode_layer.setFanSpeed(train->getSettingInPercentage("raft_base_fan_speed"));
gcode_layer.processFanSpeedAndMinimalLayerTime();
@@ -306,24 +303,21 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
int layer_nr = -n_raft_surface_layers - 1;
int layer_height = train->getSettingInMicrons("raft_interface_thickness");
z += layer_height;
int64_t comb_offset = train->getSettingInMicrons("raft_interface_line_spacing");
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings, combing_mode, 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, fan_speed_layer_time_settings, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
gcode_layer.setIsInside(false);
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
}
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, storage.raft_interface_config.getLineWidth(), train->getSettingInMicrons("raft_interface_line_spacing"), fill_overlap, fill_angle);
infill_comp.generate(raft_polygons, raftLines);
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_interface_config.getLineWidth(), train->getSettingInMicrons("raft_interface_line_spacing"), fill_overlap, fill_angle);
infill_comp.generate(raft_polygons, raftLines, in_between);
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_interface_config, SpaceFillType::Lines);
last_position_planned = gcode_layer.getLastPosition();
current_extruder_planned = gcode_layer.getExtruder();
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
gcode_layer.setFanSpeed(train->getSettingInPercentage("raft_interface_fan_speed"));
gcode_layer.processFanSpeedAndMinimalLayerTime();
@@ -335,9 +329,9 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
{ // raft surface layers
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, 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"));
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
gcode_layer.setIsInside(false);
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
@@ -346,13 +340,12 @@ 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, storage.raft_surface_config.getLineWidth(), train->getSettingInMicrons("raft_surface_line_spacing"), fill_overlap, fill_angle);
infill_comp.generate(raft_polygons, raft_lines);
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_surface_config.getLineWidth(), train->getSettingInMicrons("raft_surface_line_spacing"), fill_overlap, fill_angle);
infill_comp.generate(raft_polygons, raft_lines, in_between);
gcode_layer.addLinesByOptimizer(raft_lines, &storage.raft_surface_config, SpaceFillType::Lines);
last_position_planned = gcode_layer.getLastPosition();
current_extruder_planned = gcode_layer.getExtruder();
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
gcode_layer.setFanSpeed(train->getSettingInPercentage("raft_surface_fan_speed"));
gcode_layer.processFanSpeedAndMinimalLayerTime();
@@ -369,43 +362,17 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
layer_thickness = getSettingInMicrons("layer_height_0");
}
bool avoid_other_parts = false;
int avoid_distance = 0; // minimal avoid distance is zero
for (int extr_nr = 0; extr_nr < storage.meshgroup->getExtruderCount(); extr_nr++)
{
if (gcode.getExtruderIsUsed(extr_nr))
{
ExtruderTrain* extr = storage.meshgroup->getExtruderTrain(extr_nr);
if (extr->getSettingBoolean("travel_avoid_other_parts"))
{
avoid_other_parts = true;
avoid_distance = std::max(avoid_distance, extr->getSettingInMicrons("travel_avoid_distance"));
}
}
}
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 * 2;
int64_t comb_offset_from_outlines = storage.meshgroup->getExtruderTrain(current_extruder_planned)->getSettingInMicrons("machine_nozzle_size") * 2; // TODO: only used when there is no second wall.
int64_t z = storage.meshes[0].layers[layer_nr].printZ;
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(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);
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_thickness, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, getSettingBoolean("retraction_combing"), comb_offset_from_outlines, getSettingBoolean("travel_avoid_other_parts"), getSettingInMicrons("travel_avoid_distance"));
if (layer_nr == 0)
{ // process the skirt of the starting extruder
int extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
if (storage.skirt[extruder_nr].size() > 0)
{
gcode_layer.setExtruder(extruder_nr);
processSkirt(storage, gcode_layer, extruder_nr);
}
{
int start_extruder = 0; // TODO: make settable
gcode_layer.setExtruder(start_extruder);
processSkirt(storage, gcode_layer, start_extruder);
}
int extruder_nr_before = gcode_layer.getExtruder();
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr_before, true);
@@ -415,31 +382,25 @@ 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 (gcode.getExtruderIsUsed(extruder_nr) && !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
@@ -448,24 +409,18 @@ 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)
{
if (skirt_is_processed[extruder_nr])
{
return;
}
gcode_layer.setIsInside(false);
Polygons& skirt = storage.skirt[extruder_nr];
skirt_is_processed[extruder_nr] = true;
if (skirt.size() == 0)
if (skirt.size() > 0)
{
return;
gcode_layer.addTravel(skirt[skirt.size()-1].closestPointTo(gcode_layer.getLastPosition()));
}
gcode_layer.addTravel(skirt[skirt.size()-1].closestPointTo(gcode_layer.getLastPosition()));
gcode_layer.addPolygonsByOptimizer(skirt, &storage.skirt_config[extruder_nr]);
}
@@ -474,6 +429,7 @@ 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
}
}
@@ -496,6 +452,7 @@ 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
}
@@ -533,12 +490,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& stora
{
return;
}
if (mesh->getSettingBoolean("infill_mesh"))
{
gcode_layer.setIsInside(true);
}
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
SliceLayer* layer = &mesh->layers[layer_nr];
@@ -549,10 +501,11 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& stora
{
polygons.add(layer->parts[partNr].outline);
}
if (mesh->getSettingBoolean("magic_spiralize"))
mesh->inset0_config.spiralize = true;
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config, nullptr, z_seam_type, mesh->getSettingBoolean("magic_spiralize"));
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config);
addMeshOpenPolyLinesToGCode(storage, mesh, gcode_layer, layer_nr);
}
@@ -588,26 +541,10 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
{
return;
}
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)
{
if (part.insets.size() > 0)
{
empty = false;
break;
}
}
if (empty)
{
return;
}
}
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
PathOrderOptimizer part_order_optimizer(last_position_planned, z_seam_type);
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
@@ -624,20 +561,14 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
EFillMethod infill_pattern = mesh->getSettingAsFillMethod("infill_pattern");
int infill_angle = 45;
if ((infill_pattern == EFillMethod::LINES || infill_pattern == EFillMethod::ZIG_ZAG))
if ((infill_pattern==EFillMethod::LINES || infill_pattern==EFillMethod::ZIG_ZAG) && layer_nr & 1)
{
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;
}
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_mm");
gcode_layer.setIsInside(true); // going to print inside stuff below
double infill_overlap = mesh->getSettingInPercentage("infill_overlap");
if (mesh->getSettingBoolean("infill_before_walls"))
{
@@ -662,16 +593,12 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
if (skin_alternate_rotation && ( layer_nr / 2 ) & 1)
skin_angle -= 45;
int64_t skin_overlap = mesh->getSettingInMicrons("skin_overlap_mm");
int64_t skin_overlap = 0;
processSkin(gcode_layer, mesh, part, layer_nr, skin_overlap, skin_angle, mesh->skin_config.getLineWidth());
//After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter.
if (!mesh->getSettingBoolean("magic_spiralize") || static_cast<int>(layer_nr) < mesh->getSettingAsCount("bottom_layers"))
{
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons((mesh->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
}
gcode_layer.setIsInside(false); // we are going out of this part to a different part which might cross air
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons("machine_nozzle_size") * 1);
}
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
@@ -683,28 +610,28 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int infill_angle, int extrusion_width)
void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int infill_angle, int extrusion_width)
{
if (infill_line_distance > 0)
{
//Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers)
for(unsigned int n=1; n<part.infill_area_per_combine.size(); n++)
for(unsigned int n=1; n<part.infill_area.size(); n++)
{
EFillMethod infill_pattern = mesh->getSettingAsFillMethod("infill_pattern");
Infill infill_comp(infill_pattern, part.infill_area_per_combine[n], 0, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
Infill infill_comp(infill_pattern, part.infill_area[n], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
Polygons infill_polygons;
Polygons infill_lines;
infill_comp.generate(infill_polygons, infill_lines);
infill_comp.generate(infill_polygons, infill_lines, nullptr);
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[n]);
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[n], (infill_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
}
}
}
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)
void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int infill_angle, int extrusion_width)
{
if (infill_line_distance == 0 || part.infill_area_per_combine.size() == 0)
if (infill_line_distance == 0 || part.infill_area.size() == 0)
{
return;
}
@@ -714,8 +641,8 @@ void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMe
Polygons infill_lines;
EFillMethod pattern = mesh->getSettingAsFillMethod("infill_pattern");
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);
Infill infill_comp(pattern, part.infill_area[0], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
infill_comp.generate(infill_polygons, infill_lines, nullptr);
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[0]);
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
{
@@ -729,56 +656,41 @@ 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_0 = mesh->getSettingBoolean("travel_compensate_overlapping_walls_0_enabled");
bool compensate_overlap_x = mesh->getSettingBoolean("travel_compensate_overlapping_walls_x_enabled");
bool compensate_overlap = mesh->getSettingBoolean("travel_compensate_overlapping_walls_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"))
{
spiralize = true;
}
mesh->inset0_config.spiralize = true;
if (static_cast<int>(layer_nr) == mesh->getSettingAsCount("bottom_layers") && part.insets.size() > 0)
{ // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config, nullptr, EZSeamType::SHORTEST, false);
}
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config);
}
for(int inset_number=part.insets.size()-1; inset_number>-1; inset_number--)
{
if (inset_number == 0)
{
if (!compensate_overlap_0)
if (!compensate_overlap)
{
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type, spiralize);
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type);
}
else
{
Polygons& outer_wall = part.insets[0];
WallOverlapComputation wall_overlap_computation(outer_wall, mesh->getSettingInMicrons("wall_line_width_0"));
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->inset0_config, &wall_overlap_computation, z_seam_type, spiralize);
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->inset0_config, &wall_overlap_computation, z_seam_type);
}
}
else
{
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);
}
gcode_layer.addPolygonsByOptimizer(part.insets[inset_number], &mesh->insetX_config);
}
}
}
}
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int skin_angle, int extrusion_width)
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, double infill_overlap, int infill_angle, int extrusion_width)
{
for(SkinPart& skin_part : part.skin_parts) // TODO: optimize parts order
{
@@ -792,7 +704,6 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
if (bridge > -1)
{
pattern = EFillMethod::LINES;
skin_angle = bridge;
}
Polygons* inner_skin_outline = nullptr;
int offset_from_inner_skin_outline = 0;
@@ -806,7 +717,17 @@ 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)
@@ -814,19 +735,26 @@ 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, extrusion_width, extrusion_width, skin_overlap, skin_angle, false, false);
infill_comp.generate(skin_polygons, skin_lines);
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, mesh->getSettingBoolean("remove_overlapping_walls_x_enabled"), extrusion_width, extrusion_width, infill_overlap, infill_angle, false, false);
infill_comp.generate(skin_polygons, skin_lines, &part.perimeterGaps);
gcode_layer.addPolygonsByOptimizer(skin_polygons, &mesh->skin_config);
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (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);
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
{
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
}
else
{
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
}
gcode_layer.addLinesByOptimizer(perimeter_gap_lines, &mesh->skin_config, SpaceFillType::Lines);
}
}
@@ -836,43 +764,39 @@ void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner&
return;
int support_roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
int support_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_extruder_nr");
bool print_support_before_rest = support_infill_extruder_nr == extruder_nr_before
bool print_support_before_rest = support_extruder_nr == extruder_nr_before
|| support_roof_extruder_nr == extruder_nr_before;
// TODO: always print support after rest when only one nozzle is used for the whole meshgroup
if (print_support_before_rest != before_rest)
return;
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
if (support_layer.roofs.size() == 0 && support_layer.supportAreas.size() == 0)
{
return;
}
gcode_layer.setIsInside(false);
int current_extruder_nr = gcode_layer.getExtruder();
if (support_layer.roofs.size() > 0)
if (storage.support.supportLayers[layer_nr].roofs.size() > 0)
{
if (support_roof_extruder_nr != support_infill_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
if (support_roof_extruder_nr != support_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
{
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
}
else
{
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
}
}
else
{
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
}
}
void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
void FffGcodeWriter::addSupportLinesToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
{
if (!storage.support.generated
|| layer_nr > storage.support.layer_nr_max_filled_layer
@@ -885,11 +809,14 @@ 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");
int support_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_extruder_nr");
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_extruder_nr);
Polygons& support = storage.support.supportLayers[layer_nr].supportAreas;
std::vector<PolygonsPart> support_islands = support.splitIntoParts();
PathOrderOptimizer island_order_optimizer(gcode_layer.getLastPosition());
@@ -903,30 +830,25 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
{
PolygonsPart& island = support_islands[island_order_optimizer.polyOrder[n]];
int support_infill_overlap = 0; // support infill should not be expanded outward
double 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 = 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);
}
Polygons boundary;
PolygonUtils::offsetSafe(island, -extrusion_width / 2, extrusion_width, boundary, remove_overlapping_perimeters);
gcode_layer.addPolygonsByOptimizer(boundary, &storage.support_config);
offset_from_outline = -extrusion_width;
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_overlap = storage.meshgroup->getExtruderTrain(support_extruder_nr)->getSettingInPercentage("infill_overlap"); // support lines area should be expanded outward to overlap with the boundary polygon
}
Infill infill_comp(support_pattern, island, offset_from_outline, extrusion_width, support_line_distance, support_infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
Infill infill_comp(support_pattern, island, offset_from_outline, remove_overlapping_perimeters, extrusion_width, support_line_distance, infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
Polygons support_polygons;
Polygons support_lines;
infill_comp.generate(support_polygons, support_lines);
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);
}
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);
}
}
@@ -945,21 +867,12 @@ 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 (all_roofs_are_low || pattern == EFillMethod::TRIANGLES)
else if (getSettingInMicrons("support_roof_height") < 2 * getSettingInMicrons("layer_height") || pattern == EFillMethod::TRIANGLES)
{
fillAngle = 90; // perpendicular to support lines
}
@@ -967,13 +880,13 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
{
fillAngle = 45 + (layer_nr % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
}
int support_skin_overlap = 0; // the roofs should never be expanded outwards
double infill_overlap = 0; // the roofs should never be expanded outwards
int outline_offset = 0;
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, storage.support_roof_config.getLineWidth(), support_line_distance, support_skin_overlap, fillAngle, false, true);
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, false, storage.support_roof_config.getLineWidth(), support_line_distance, infill_overlap, fillAngle, false, true);
Polygons support_polygons;
Polygons support_lines;
infill_comp.generate(support_polygons, support_lines);
infill_comp.generate(support_polygons, support_lines, nullptr);
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_roof_config);
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_roof_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
@@ -990,7 +903,7 @@ void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanne
if (extruder_changed)
{
if (layer_nr == 0 && !skirt_is_processed[extruder_nr])
if (layer_nr == 0)
{
processSkirt(storage, gcode_layer, extruder_nr);
}
@@ -1005,7 +918,7 @@ void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanne
void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int prev_extruder)
{
if (!getSettingBoolean("prime_tower_enable"))
if (getSettingInMicrons("prime_tower_size") < 1)
{
return;
}
@@ -1020,22 +933,21 @@ void FffGcodeWriter::finalize()
{
if (CommandSocket::isInstantiated())
{
double print_time = gcode.getTotalPrintTime();
std::vector<double> filament_used;
for (int extr_nr = 0; extr_nr < getSettingAsCount("machine_extruder_count"); extr_nr++)
std::ostringstream prefix;
prefix << ";FLAVOR:" << toString(gcode.getFlavor()) << "\n";
prefix << ";TIME:" << int(gcode.getTotalPrintTime()) << "\n";
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
{
filament_used.emplace_back(gcode.getTotalFilamentUsed(extr_nr));
prefix << ";MATERIAL:" << int(gcode.getTotalFilamentUsed(0)) << "\n";
prefix << ";MATERIAL2:" << int(gcode.getTotalFilamentUsed(1)) << "\n";
}
std::string prefix = gcode.getFileHeader(&print_time, filament_used);
CommandSocket::getInstance()->sendGCodePrefix(prefix);
CommandSocket::getInstance()->sendGCodePrefix(prefix.str());
}
gcode.finalize(getSettingString("machine_end_gcode").c_str());
for (int e = 0; e < getSettingAsCount("machine_extruder_count"); e++)
{
gcode.finalize(getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
for(int e=0; e<getSettingAsCount("machine_extruder_count"); e++)
gcode.writeTemperatureCommand(e, 0, false);
}
gcode.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.
@@ -1047,4 +959,3 @@ void FffGcodeWriter::finalize()
}//namespace cura
+67 -148
Ver Arquivo
@@ -37,64 +37,40 @@ class FffGcodeWriter : public SettingsMessenger, NoCopy
{
friend class FffProcessor; // cause WireFrame2Gcode uses the member [gcode] (TODO)
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.
/*
* Buffer for all layer plans (of type GCodePlanner)
*
* The layer plans are buffered so that we can start heating up a nozzle several layers before it needs to be used.
* Another reason is to perform Auto Temperature.
*/
LayerPlanBuffer layer_plan_buffer;
/*!
* The class holding the current state of the gcode being written.
*
* It holds information such as the last written position etc.
*/
int max_object_height;
int meshgroup_number; //!< used for sequential printing of objects
LayerPlanBuffer layer_plan_buffer;
GCodeExport gcode;
/*!
* The gcode file to write to when using CuraEngine as command line tool.
*/
std::ofstream output_file;
/*!
* Layer number of the last layer in which a prime tower has been printed per extruder train.
*
* This is recorded per extruder to account for a prime tower per extruder, instead of the mixed prime tower.
*/
int last_prime_tower_poly_printed[MAX_EXTRUDERS];
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.
FanSpeedLayerTimeSettings fan_speed_layer_time_settings;
Point last_position_planned; //!< The position of the head before planning the next layer
int current_extruder_planned; //!< The extruder train in use before planning the next layer
bool is_inside_mesh_layer_part; //!< Whether the last position was inside a layer part (used in combing)
public:
FffGcodeWriter(SettingsBase* settings_)
: SettingsMessenger(settings_)
, layer_plan_buffer(this, gcode)
, last_position_planned(no_point)
, current_extruder_planned(0) // changed somewhere early in FffGcodeWriter::writeGCode
, is_inside_mesh_layer_part(false)
, current_extruder_planned(0) // TODO: make configurable
{
meshgroup_number = 1;
max_object_height = 0;
for (unsigned int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
{
skirt_is_processed[extruder_nr] = false;
}
}
void resetFileNumber()
{
meshgroup_number = 1;
}
/*!
* Set the target to write gcode to: to a file.
*
* Used when CuraEngine is used as command line tool.
*
* \param filename The filename of the file to which to write the gcode.
*/
bool setTargetFile(const char* filename)
{
output_file.open(filename);
@@ -105,108 +81,59 @@ public:
}
return false;
}
/*!
* Set the target to write gcode to: an output stream.
*
* Used when CuraEngine is NOT used as command line tool.
*
* \param stream The stream to write gcode to.
*/
void setTargetStream(std::ostream* stream)
{
gcode.setOutputStream(stream);
}
/*!
* Get the total extruded volume for a specific extruder in mm^3
*
* Retractions and unretractions don't contribute to this.
*
* \param extruder_nr The extruder number for which to get the total netto extruded volume
* \return total filament printed in mm^3
*/
double getTotalFilamentUsed(int extruder_nr)
double getTotalFilamentUsed(int e)
{
return gcode.getTotalFilamentUsed(extruder_nr);
return gcode.getTotalFilamentUsed(e);
}
/*!
* Get the total estimated print time in seconds
*
* \return total print time in seconds
*/
double getTotalPrintTime()
{
return gcode.getTotalPrintTime();
}
/*!
* Write all the gcode for the current meshgroup.
* This is the primary function of this class.
*
* \param[in] storage The data storage from which to get the polygons to print and the areas to fill.
* \param timeKeeper The stop watch to see how long it takes for each of the stages in the slicing process.
*/
void writeGCode(SliceDataStorage& storage, TimeKeeper& timeKeeper);
private:
/*!
* Set the FffGcodeWriter::fan_speed_layer_time_settings by retrieving all settings from the global/per-meshgroup settings.
*/
void setConfigFanSpeedLayerTime();
/*!
* Create and set the SliceDataStorage::coasting_config for each extruder.
*
* \param[out] storage The data storage to which to save the configuration
*/
void setConfigCoasting(SliceDataStorage& storage);
/*!
* Set the retraction config globally, per extruder and per mesh.
*
* \param[out] storage The data storage to which to save the configurations
*/
//Setup the retraction parameters.
void setConfigRetraction(SliceDataStorage& storage);
/*!
* Initialize the GcodePathConfig config parameters which don't change over all layers, for each feature.
*
* The features are: skirt, support and for each mesh: outer wall, inner walls, skin, infill (and combined infill)
*
* \param[out] storage The data storage to which to save the configurations
* initialize GcodePathConfig config parameters which don't change over all layers
*/
void initConfigs(SliceDataStorage& storage);
/*!
* Set temperatures and perform initial priming.
*
* Write a stub header if CuraEngine is in command line tool mode. (Cause writing the header afterwards would entail moving all gcode down.)
*
* \param[in] storage where the slice data is stored.
* \param storage Input: where the slice data is stored.
*/
void processStartingCode(SliceDataStorage& storage);
/*!
* Move up and over the already printed meshgroups to print the next meshgroup.
*
* \param[in] storage where the slice data is stored.
* Move up and over the just printed model to print the next model.
* \param storage Input: where the slice data is stored.
*/
void processNextMeshGroupCode(SliceDataStorage& storage);
/*!
* Add raft layer plans onto the FffGcodeWriter::layer_plan_buffer
*
* \param[in] storage where the slice data is stored.
* Add raft gcode.
* \param storage Input: where the slice data is stored.
* \param total_layers The total number of layers.
*/
void processRaft(SliceDataStorage& storage, unsigned int total_layers);
/*!
* Convert the polygon data of a layer into a layer plan on the FffGcodeWriter::layer_plan_buffer
*
* \param[in] storage where the slice data is stored.
* Add a layer to the gcode.
* \param storage Input: where the slice data is stored.
* \param layer_nr The index of the layer to write the gcode of.
* \param total_layers The total number of layers.
* \param has_raft Whether a raft is used for this print.
@@ -214,27 +141,24 @@ private:
void processLayer(SliceDataStorage& storage, unsigned int layer_nr, unsigned int total_layers, bool has_raft);
/*!
* Add the skirt to the layer plan \p gcodeLayer.
*
* \param[in] storage where the slice data is stored.
* Add the skirt to the gcode.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param extruder_nr The extrudewr train for which to process the skirt
*/
void processSkirt(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int extruder_nr);
/*!
* Adds the ooze shield to the layer plan \p gcodeLayer.
*
* \param[in] storage where the slice data is stored.
* Adds the ooze shield to the print.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*/
void processOozeShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
/*!
* Adds the draft protection screen to the layer plan \p gcodeLayer.
*
* \param[in] storage where the slice data is stored.
* Adds the draft protection screen to the print.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*/
@@ -242,18 +166,16 @@ private:
/*!
* Calculate in which order to print the meshes.
*
* \param[in] storage where the slice data is stored.
* \param storage Input: where the slice data is stored.
* \param current_extruder The current extruder with which we last printed
* \return A vector of mesh indices ordered on print order.
*/
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int current_extruder);
/*!
* Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer in mesh surface mode.
*
* \param[in] storage where the slice data is stored.
* \param mesh The mesh to add to the layer plan \p gcodeLayer.
* Add a single layer from a single mesh-volume to the GCode in mesh surface mode.
* \param storage Input: where the slice data is stored.
* \param mesh The mesh to add to the gcode.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*
@@ -261,10 +183,9 @@ private:
void addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcodeLayer, int layer_nr);
/*!
* Add the open polylines from a single layer from a single mesh-volume to the layer plan \p gcodeLayer for mesh the surface modes.
*
* \param[in] storage where the slice data is stored.
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
* Add the open polylines from a single layer from a single mesh-volume to the GCode for mesh surface mode.
* \param storage Input: where the slice data is stored.
* \param mesh The mesh for which to add to the gcode.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*
@@ -272,10 +193,9 @@ private:
void addMeshOpenPolyLinesToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr);
/*!
* Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer.
*
* \param[in] storage where the slice data is stored.
* \param mesh The mesh to add to the layer plan \p gcodeLayer.
* Add a single layer from a single mesh-volume to the GCode.
* \param storage Input: where the slice data is stored.
* \param mesh The mesh to add to the gcode.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*
@@ -283,36 +203,35 @@ private:
void addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcodeLayer, int layer_nr);
/*!
* Add thicker (multiple layers) sparse infill for a given part in a layer plan.
*
* Add thicker (multiple layers) sparse infill for a given part in a layer.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
* \param mesh The mesh for which to add to the gcode.
* \param part The part for which to create gcode
* \param layer_nr The current layer number.
* \param infill_line_distance The distance between the infill lines
* \param infill_overlap The distance by which the infill overlaps with the wall insets.
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
* \param fillAngle The angle in the XY plane at which the infill is generated.
* \param extrusionWidth extrusionWidth
*/
void processMultiLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int fillAngle, int extrusionWidth);
void processMultiLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int fillAngle, int extrusionWidth);
/*!
* Add normal sparse infill for a given part in a layer.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
* \param mesh The mesh for which to add to the gcode.
* \param part The part for which to create gcode
* \param layer_nr The current layer number.
* \param infill_line_distance The distance between the infill lines
* \param infill_overlap The distance by which the infill overlaps with the wall insets.
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
* \param fillAngle The angle in the XY plane at which the infill is generated.
* \param extrusionWidth extrusionWidth
*/
void processSingleLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int fillAngle, int extrusionWidth);
void processSingleLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int fillAngle, int extrusionWidth);
/*!
* Generate the insets for the walls of a given layer part.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
* \param mesh The mesh for which to add to the gcode.
* \param part The part for which to create gcode
* \param layer_nr The current layer number.
* \param z_seam_type dir3ective for where to start the outer paerimeter of a part
@@ -323,34 +242,34 @@ private:
/*!
* Add the gcode of the top/bottom skin of the given part.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
* \param mesh The mesh for which to add to the gcode.
* \param part The part for which to create gcode
* \param layer_nr The current layer number.
* \param skin_overlap The distance by which the skin overlaps with the wall insets.
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
* \param fillAngle The angle in the XY plane at which the infill is generated.
* \param extrusionWidth extrusionWidth
*/
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle, int extrusion_width);
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, double infill_overlap, int infill_angle, int extrusion_width);
/*!
* Add the support to the layer plan \p gcodeLayer of the current layer.
* \param[in] storage where the slice data is stored.
* Add the support to the gcode of the current layer.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
* \param extruder_nr_before The extruder number at the start of the layer (before other print parts aka the rest)
* \param before_rest Whether the function has been called before adding the rest to the layer plan \p gcodeLayer, or after.
* \param before_rest Whether the function has been called before adding the rest to the gcode, or after.
*/
void addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr_before, bool before_rest);
/*!
* Add the support lines/walls to the layer plan \p gcodeLayer of the current layer.
* \param[in] storage where the slice data is stored.
* Add the support lines/walls to the gcode of the current layer.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*/
void addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
void addSupportLinesToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
/*!
* Add the support roofs to the layer plan \p gcodeLayer of the current layer.
* \param[in] storage where the slice data is stored.
* Add the support roofs to the gcode of the current layer.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*/
@@ -361,7 +280,7 @@ private:
*
* On layer 0 this function adds the skirt for the nozzle it switches to, instead of the prime tower.
*
* \param[in] storage where the slice data is stored.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
* \param extruder_nr The extruder to which to switch
@@ -370,7 +289,7 @@ private:
/*!
* Add the prime tower gcode for the current layer.
* \param[in] storage where the slice data is stored.
* \param storage Input: where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
* \param prev_extruder The current extruder with which we last printed.
+132 -332
Ver Arquivo
@@ -1,7 +1,6 @@
#include "FffPolygonGenerator.h"
#include <algorithm>
#include <map> // multimap (ordered map allowing duplicate keys)
#include "slicer.h"
#include "utils/gettime.h"
@@ -10,19 +9,14 @@
#include "support.h"
#include "multiVolumes.h"
#include "layerPart.h"
#include "WallsComputation.h"
#include "inset.h"
#include "skirt.h"
#include "skin.h"
#include "infill.h"
#include "raft.h"
#include "debug.h"
#include "progress/Progress.h"
#include "Progress.h"
#include "PrintFeature.h"
#include "progress/ProgressEstimator.h"
#include "progress/ProgressStageEstimator.h"
#include "progress/ProgressEstimatorLinear.h"
#include "utils/SVG.h" // debug
namespace cura
{
@@ -30,6 +24,9 @@ namespace cura
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
{
if (CommandSocket::isInstantiated())
CommandSocket::getInstance()->beginSendSlicedObject();
if (!sliceModel(meshgroup, timeKeeper, storage))
{
return false;
@@ -49,22 +46,27 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
storage.model_size = storage.model_max - storage.model_min;
log("Slicing model...\n");
int initial_layer_thickness = getSettingInMicrons("layer_height_0");
int initial_layer_thickness = meshgroup->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.\n", initial_layer_thickness);
logError("Initial layer height %i is disallowed.",initial_layer_thickness);
return false;
}
int layer_thickness = getSettingInMicrons("layer_height");
int layer_thickness = meshgroup->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.\n", layer_thickness);
logError("Layer height %i is disallowed.",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!
}
@@ -91,8 +93,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
//carveMultipleVolumes(storage.meshes);
generateMultipleVolumesOverlap(slicerList);
// TODO!!! dont generate multi volume overlap with infill meshes!
generateMultipleVolumesOverlap(slicerList, getSettingInMicrons("multiple_mesh_overlap"));
storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
for(unsigned int meshIdx=0; meshIdx < slicerList.size(); meshIdx++)
@@ -100,32 +101,24 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
storage.meshes.emplace_back(&meshgroup->meshes[meshIdx]); // new mesh in storage had settings from the Mesh
SliceMeshStorage& meshStorage = storage.meshes.back();
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
createLayerParts(meshStorage, slicerList[meshIdx], mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes"));
delete slicerList[meshIdx];
bool has_raft = getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
bool has_raft = meshStorage.getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
//Add the raft offset to each layer.
for(unsigned int layer_nr=0; layer_nr<meshStorage.layers.size(); layer_nr++)
{
SliceLayer& layer = meshStorage.layers[layer_nr];
meshStorage.layers[layer_nr].printZ +=
getSettingInMicrons("layer_height_0")
meshStorage.getSettingInMicrons("layer_height_0")
- initial_slice_z;
if (has_raft)
{
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("adhesion_extruder_nr"));
layer.printZ +=
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
}
meshStorage.getSettingInMicrons("raft_base_thickness")
+ meshStorage.getSettingInMicrons("raft_interface_thickness")
+ meshStorage.getSettingAsCount("raft_surface_layers") * getSettingInMicrons("raft_surface_thickness")
+ meshStorage.getSettingInMicrons("raft_airgap");
}
@@ -136,70 +129,44 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? getSettingInMicrons("layer_height_0") : getSettingInMicrons("layer_height"));
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? meshStorage.getSettingInMicrons("layer_height_0") : meshStorage.getSettingInMicrons("layer_height"));
}
}
Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size());
}
Progress::messageProgressStage(Progress::Stage::INSET, &timeKeeper);
return true;
}
void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& time_keeper)
{
// compute layer count and remove first empty layers
// there is no separate progress stage for removeEmptyFisrtLayer (TODO)
unsigned int total_layers = 0;
size_t total_layers = 0;
for (SliceMeshStorage& mesh : storage.meshes)
{
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());
total_layers = std::max<unsigned int>(total_layers, mesh.layers.size());
}
//layerparts2HTML(storage, "output/output.html");
// 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)
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
{
log("Stopping process because there are no non-empty layers.\n");
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");
return;
}
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
AreaSupport::generateSupportAreas(storage, total_layers);
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
AreaSupport::generateSupportAreas(storage, total_layers);
/*
if (storage.support.generated)
{
@@ -213,256 +180,83 @@ 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);
}
unsigned int combined_infill_layers = storage.getSettingInMicrons("infill_sparse_thickness") / std::max(storage.getSettingInMicrons("layer_height"),1); //How many infill layers to combine to obtain the requested sparse thickness.
for(SliceMeshStorage& mesh : storage.meshes)
{
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);
// meshes post processing
for (SliceMeshStorage& mesh : storage.meshes)
{
processDerivedWallsSkinInfill(mesh, total_layers);
}
}
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate)
{
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
if (mesh.getSettingBoolean("infill_mesh"))
{
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.
{
processSkinsAndInfill(mesh, layer_number);
}
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;
Polygons new_open_polylines;
for (unsigned int other_mesh_idx : mesh_order)
{
if (other_mesh_idx == mesh_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)
{
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;
}
}
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
for (SliceLayerPart& other_part : other_layer.parts)
{
Polygons& infill = other_part.infill_area;
Polygons new_open_polylines_here = layer.openPolyLines.intersectPolylines(infill);
new_open_polylines.add(new_open_polylines_here);
if (layer_idx == 37)
{
Polygons wrong_line;
{ PolygonRef line = wrong_line.newPoly();
line.emplace_back(130422,112525);
line.emplace_back(130272,112525); }
{ PolygonRef line = wrong_line.newPoly();
line.emplace_back(130422,112525);
line.emplace_back(130272,112525); }
{ PolygonRef line = wrong_line.newPoly();
line.emplace_back(132322,112525);
line.emplace_back(130422,112525); }
Polygons intersected = wrong_line.intersectPolylines(infill);
{
AABB aabb(infill);
aabb.expand(MM2INT(5.0));
SVG svg("debug_layer_37.html",aabb);
svg.writeAreas(infill);
svg.writeLines(layer.openPolyLines, SVG::Color::BLUE);
svg.writeLines(new_open_polylines_here, SVG::Color::GREEN);
// svg.writePoints(wrong_line, true, 1);
}
{
AABB aabb(infill);
aabb.expand(MM2INT(5.0));
SVG svg("debug_layer_37_part.html",aabb);
svg.writeAreas(infill);
svg.writeLines(wrong_line, SVG::Color::BLUE);
svg.writeLines(intersected, SVG::Color::GREEN);
// svg.writePoints(wrong_line, true, 1);
}
{
AABB aabb(infill);
aabb.expand(MM2INT(5.0));
SVG svg("debug_layer_37_polylines.html",aabb);
svg.writeLines(layer.openPolyLines, SVG::Color::BLACK);
// svg.writePoints(wrong_line, true, 1);
}
std::cerr << "start\n";
for (PolygonRef poly : layer.openPolyLines)
{
std::cerr << " { PolygonRef line = wrong_line.newPoly();\n line.emplace_back" << poly[0] << ";\n line.emplace_back"<<poly[1]<<"; }" << std::endl;
}
for (PolygonRef poly : infill)
for (Point& p : poly)
std::cerr << " infill point " << p << std::endl;
std::cerr << "written\n";
}
// TODO: Don't adjust infill area for open polylines? \/
// infill = infill.difference(layer.openPolyLines.offsetPolyLine(mesh.getSettingInMicrons("wall_line_width") / 2));
// 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);
}
layer.openPolyLines = new_open_polylines;
}
processDraftShield(storage, total_layers);
}
processPlatformAdhesion(storage);
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"))
for(SliceMeshStorage& mesh : storage.meshes)
{
processFuzzyWalls(mesh);
}
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
{
SliceLayer& layer = mesh.layers[1];
{
AABB aabb(layer.openPolyLines);
aabb.expand(MM2INT(5.0));
SVG svg("debug_layer_37_polylines.html",aabb);
svg.writeLines(layer.openPolyLines, SVG::Color::BLACK);
// svg.writePoints(wrong_line, true, 1);
}
}
}
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)
if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
{
Polygons segments;
for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
processFuzzyWalls(mesh);
}
}
}
void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int layer_nr)
{
for(SliceMeshStorage& mesh : storage.meshes)
{
SliceLayer* layer = &mesh.layers[layer_nr];
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
{
int inset_count = mesh.getSettingAsCount("wall_line_count");
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
inset_count += 5;
int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
inset_count += layer_nr % 2;
generateInsets(layer, mesh.getSettingInMicrons("machine_nozzle_size"), line_width_0, line_width_x, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
}
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
for (PolygonRef polyline : layer->openPolyLines)
{
PolygonRef segment = segments.newPoly();
segment.add(polyline[point_idx-1]);
segment.add(polyline[point_idx]);
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, const int layer_height, unsigned int& total_layers)
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, 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++)
@@ -503,27 +297,34 @@ void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, cons
}
}
void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr)
void FffPolygonGenerator::processSkinsAndInfill(SliceDataStorage& storage, unsigned int layer_nr)
{
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)
// TODO: only compute the area if there are any infill meshes to be computed
for(SliceMeshStorage& mesh : storage.meshes)
{
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)
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)
{
infill_skin_overlap = skin_extrusion_width / 2;
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);
}
}
generateInfill(layer_nr, mesh, innermost_wall_extrusion_width, infill_skin_overlap, wall_line_count);
}
}
@@ -584,20 +385,19 @@ 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 (train->getSettingInMicrons("draft_shield_height") == 0)
if (getSettingInMicrons("draft_shield_height") == 0)
{ // draft screen replaces skirt
generateSkirt(storage, train->getSettingInMicrons("skirt_gap"), train->getSettingAsCount("skirt_line_count"), train->getSettingInMicrons("skirt_minimal_length"));
generateSkirt(storage, getSettingInMicrons("skirt_gap"), getSettingAsCount("skirt_line_count"), getSettingInMicrons("skirt_minimal_length"));
}
break;
case EPlatformAdhesion::BRIM:
generateSkirt(storage, 0, train->getSettingAsCount("brim_line_count"), train->getSettingInMicrons("skirt_minimal_length"));
generateSkirt(storage, 0, getSettingAsCount("brim_line_count"), getSettingInMicrons("skirt_minimal_length"));
break;
case EPlatformAdhesion::RAFT:
generateRaft(storage, train->getSettingInMicrons("raft_margin"));
generateRaft(storage, getSettingInMicrons("raft_margin"));
break;
}
@@ -639,7 +439,7 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist)
{
int r = rand() % (fuzziness * 2) - fuzziness;
Point perp_to_p0p1 = turn90CCW(p0p1);
Point perp_to_p0p1 = crossZ(p0p1);
Point fuzz = normal(perp_to_p0p1, r);
Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz;
result.add(pa);
+6 -41
Ver Arquivo
@@ -6,12 +6,10 @@
#include "utils/polygonUtils.h"
#include "utils/NoCopy.h"
#include "utils/gettime.h"
#include "settings/settings.h"
#include "settings.h"
#include "sliceDataStorage.h"
#include "commandSocket.h"
#include "PrintFeature.h"
#include "progress/ProgressEstimator.h"
#include "progress/ProgressStageEstimator.h"
namespace cura
{
@@ -66,53 +64,20 @@ 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, const int layer_height, unsigned int& total_layers);
void removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int total_layers);
/*!
* Generate the inset polygons which form the walls.
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param layer_nr The layer for which to generate the insets.
*/
void processInsets(SliceMeshStorage& mesh, unsigned int layer_nr);
void processInsets(SliceDataStorage& storage, unsigned int layer_nr);
/*!
* Generate the outline of the ooze shield.
@@ -123,10 +88,10 @@ private:
/*!
* Generate the skin areas.
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param layer_nr The layer for which to generate the skin areas.
*/
void processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr);
void processSkinsAndInfill(SliceDataStorage& storage, unsigned int layer_nr);
/*!
* Generate the polygons where the draft screen should be.
+8 -35
Ver Arquivo
@@ -5,18 +5,6 @@ namespace cura
FffProcessor FffProcessor::instance; // definition must be in cpp
FffProcessor::FffProcessor()
: polygon_generator(this)
, gcode_writer(this)
, meshgroup_number(0)
{
}
int FffProcessor::getMeshgroupNr()
{
return meshgroup_number;
}
std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_meshgroup)
{
@@ -39,7 +27,7 @@ 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.getSettingAsIndex("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
sstream << " -e" << mesh.getSettingAsCount("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
}
sstream << "\n";
return sstream.str();
@@ -70,30 +58,19 @@ bool FffProcessor::processFiles(const std::vector< std::string >& files)
bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
{
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, meshgroup_number == 0).c_str()); }
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, first_meshgroup).c_str()); }
time_keeper.restart();
if (!meshgroup)
return false;
TimeKeeper time_keeper_total;
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)
if (meshgroup->meshes.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, meshgroup_number == 0);
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
return true;
}
@@ -126,16 +103,12 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
CommandSocket::getInstance()->sendLayerData();
CommandSocket::getInstance()->endSendSlicedObject();
}
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
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
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
first_meshgroup = false;
return true;
}
+23 -108
Ver Arquivo
@@ -1,13 +1,13 @@
#ifndef FFF_PROCESSOR_H
#define FFF_PROCESSOR_H
#include "settings/settings.h"
#include "settings.h"
#include "FffGcodeWriter.h"
#include "FffPolygonGenerator.h"
#include "commandSocket.h"
#include "Weaver.h"
#include "Wireframe2gcode.h"
#include "progress/Progress.h"
#include "Progress.h"
#include "utils/gettime.h"
#include "utils/NoCopy.h"
@@ -19,152 +19,67 @@ namespace cura {
class FffProcessor : public SettingsBase , NoCopy
{
private:
/*!
* The FffProcessor used for the (current) slicing (The instance of this singleton)
*/
static FffProcessor instance;
FffProcessor();
FffProcessor()
: polygon_generator(this)
, gcode_writer(this)
, first_meshgroup(true)
{
}
public:
/*!
* Get the instance
* \return The instance
*/
static FffProcessor* getInstance()
{
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.
*/
FffPolygonGenerator polygon_generator;
/*!
* The gcode writer, which generates paths in layer plans in a buffer, which converts these paths into gcode commands.
*/
FffGcodeWriter gcode_writer;
/*!
* The index of the meshgroup currently being processed, starting at zero.
*/
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.
*
* Used in debugging.
*/
bool first_meshgroup;
std::string profile_string = "";
/*!
* Get all settings for the current meshgroup in the format by which CuraEngine is called via the command line.
*
* Also includes all global settings if this is the first meshgroup.
*
* Used in debugging.
*
* \param meshgroup The meshgroup for which to stringify all settings
* \param first_meshgroup Whether this is the first meshgroup and all global settigns should be included as well
*/
std::string getAllSettingsString(MeshGroup& meshgroup, bool first_meshgroup);
public:
/*!
* Get a string containing all setting values passed to the engine in the format by which CuraEngine is called via the command line.
*
* \return A string containing all setting values passed to the engine in the format by which CuraEngine is called via the command line.
*/
std::string getProfileString() { return profile_string; }
/*!
* The stop watch used to time how long the different stages take to compute.
*/
TimeKeeper time_keeper; // TODO: use singleton time keeper
/*!
* Reset the meshgroup number to the first meshgroup to start a new slicing.
*/
void resetMeshGroupNumber()
void resetFileNumber()
{
meshgroup_number = 0;
gcode_writer.resetFileNumber();
}
/*!
* Set the target to write gcode to: to a file.
*
* Used when CuraEngine is used as command line tool.
*
* \param filename The filename of the file to which to write the gcode.
*/
bool setTargetFile(const char* filename)
{
return gcode_writer.setTargetFile(filename);
}
/*!
* Set the target to write gcode to: an output stream.
*
* Used when CuraEngine is NOT used as command line tool.
*
* \param stream The stream to write gcode to.
*/
void setTargetStream(std::ostream* stream)
{
return gcode_writer.setTargetStream(stream);
}
/*!
* Get the total extruded volume for a specific extruder in mm^3
*
* Retractions and unretractions don't contribute to this.
*
* \param extruder_nr The extruder number for which to get the total netto extruded volume
* \return total filament printed in mm^3
*/
double getTotalFilamentUsed(int extruder_nr)
double getTotalFilamentUsed(int e)
{
return gcode_writer.getTotalFilamentUsed(extruder_nr);
return gcode_writer.getTotalFilamentUsed(e);
}
/*!
* Get the total estimated print time in seconds
*
* \return total print time in seconds
*/
double getTotalPrintTime()
{
return gcode_writer.getTotalPrintTime();
}
/*!
* Add the end gcode and set all temperatures to zero.
*/
void finalize()
{
gcode_writer.finalize();
}
/*!
* Process all files into one meshgroup
*
* \warning Unused!
*/
bool processFiles(const std::vector<std::string> &files);
/*!
* Generate gcode for a given \p meshgroup
* The primary function of this class.
*
* \param meshgroup The meshgroup for which to generate gcode
* \return Whether this function succeeded
*/
bool processMeshGroup(MeshGroup* meshgroup);
};
-111
Ver Arquivo
@@ -1,111 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "utils/intpoint.h" // INT2MM
#include "GCodePathConfig.h"
namespace cura
{
GCodePathConfig::BasicConfig::BasicConfig()
: speed(0)
, acceleration(0)
, jerk(0)
, line_width(0)
, flow(100)
{
}
GCodePathConfig::BasicConfig::BasicConfig(double speed, double acceleration, double jerk, int line_width, double flow)
: speed(speed)
, acceleration(acceleration)
, jerk(jerk)
, line_width(line_width)
, flow(flow)
{
}
void GCodePathConfig::BasicConfig::set(double speed, double acceleration, double jerk, int line_width, double flow)
{
this->speed = speed;
this->acceleration = acceleration;
this->jerk = jerk;
this->line_width = line_width;
this->flow = flow;
}
GCodePathConfig::GCodePathConfig(PrintFeatureType type)
: extrusion_mm3_per_mm(0.0)
, type(type)
{
}
void GCodePathConfig::init(double speed, double acceleration, double jerk, int line_width, double flow)
{
iconic_config.set(speed, acceleration, jerk, line_width, flow);
current_config = iconic_config;
}
void GCodePathConfig::setLayerHeight(int layer_height)
{
this->layer_thickness = layer_height;
calculateExtrusion();
}
void GCodePathConfig::smoothSpeed(GCodePathConfig::BasicConfig first_layer_config, int layer_nr, double max_speed_layer)
{
current_config.speed = (iconic_config.speed * layer_nr) / max_speed_layer + (first_layer_config.speed * (max_speed_layer - layer_nr) / max_speed_layer);
current_config.acceleration = (iconic_config.acceleration * layer_nr) / max_speed_layer + (first_layer_config.acceleration * (max_speed_layer - layer_nr) / max_speed_layer);
current_config.jerk = (iconic_config.jerk * layer_nr) / max_speed_layer + (first_layer_config.jerk * (max_speed_layer - layer_nr) / max_speed_layer);
}
void GCodePathConfig::setSpeedIconic()
{
current_config.speed = iconic_config.speed;
current_config.acceleration = iconic_config.acceleration;
current_config.jerk = iconic_config.jerk;
}
double GCodePathConfig::getExtrusionMM3perMM()
{
return extrusion_mm3_per_mm;
}
double GCodePathConfig::getSpeed()
{
return current_config.speed;
}
double GCodePathConfig::getAcceleration()
{
return current_config.acceleration;
}
double GCodePathConfig::getJerk()
{
return current_config.jerk;
}
int GCodePathConfig::getLineWidth()
{
return current_config.line_width;
}
bool GCodePathConfig::isTravelPath()
{
return current_config.line_width == 0;
}
double GCodePathConfig::getFlowPercentage()
{
return current_config.flow;
}
void GCodePathConfig::calculateExtrusion()
{
extrusion_mm3_per_mm = INT2MM(current_config.line_width) * INT2MM(layer_thickness) * double(current_config.flow) / 100.0;
}
}//namespace cura
-112
Ver Arquivo
@@ -1,112 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef G_CODE_PATH_CONFIG_H
#define G_CODE_PATH_CONFIG_H
#include "RetractionConfig.h"
#include "PrintFeature.h"
namespace cura
{
/*!
* The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
*/
class GCodePathConfig
{
friend class GCodePlannerTest;
public:
/*!
* The path config settings which may change from layer to layer
*/
struct BasicConfig
{
double speed; //!< movement speed (mm/s)
double acceleration; //!< acceleration of head movement (mm/s^2)
double jerk; //!< jerk of the head movement (around stand still) (mm/s^3)
int line_width; //!< width of the line extruded
double flow; //!< extrusion flow modifier in %
BasicConfig(); //!< basic contructor initializing with inaccurate values
BasicConfig(double speed, double acceleration, double jerk, int line_width, double flow); //!< basic contructor initializing all values
void set(double speed, double acceleration, double jerk, int line_width, double flow); //!< Set all config values
};
private:
BasicConfig iconic_config; //!< The basic path configuration iconic to this print feature type
BasicConfig current_config; //!< The current path configuration for the current layer
int layer_thickness; //!< current layer height in micron
double extrusion_mm3_per_mm;//!< current mm^3 filament moved per mm line traversed
public:
const PrintFeatureType type; //!< name of the feature type
/*!
* Basic constructor.
*/
GCodePathConfig(PrintFeatureType type);
/*!
* Initialize some of the member variables.
*
* \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, double acceleration, double jerk, int line_width, double flow);
/*!
* Set the layer height and (re)compute the extrusion_per_mm
*/
void setLayerHeight(int layer_height);
/*!
* Set the speed to somewhere between the speed of @p first_layer_config and the iconic speed.
*
* \warning This functions should not be called with @p layer_nr > @p max_speed_layer !
*
* \param first_layer_config The speed settings at layer zero
* \param layer_nr The layer number
* \param max_speed_layer The layer number for which the speed_iconic should be used.
*/
void smoothSpeed(BasicConfig first_layer_config, int layer_nr, double max_speed_layer);
/*!
* Set the speed config to the iconic speed config, i.e. the normal speed of the feature type for which this is a config.
*
* Does the same for acceleration and jerk.
*/
void setSpeedIconic();
/*!
* Can only be called after the layer height has been set (which is done while writing the gcode!)
*/
double getExtrusionMM3perMM();
/*!
* Get the movement speed in mm/s
*/
double getSpeed();
/*!
* Get the current acceleration of this config
*/
double getAcceleration();
/*!
* Get the current jerk of this config
*/
double getJerk();
int getLineWidth();
bool isTravelPath();
double getFlowPercentage();
private:
void calculateExtrusion();
};
}//namespace cura
#endif // G_CODE_PATH_CONFIG_H
+100 -124
Ver Arquivo
@@ -3,7 +3,6 @@
#include "LayerPlanBuffer.h"
#include "gcodeExport.h"
#include "utils/logoutput.h"
#include "FffProcessor.h"
namespace cura {
@@ -17,11 +16,9 @@ void LayerPlanBuffer::flush()
}
while (!buffer.empty())
{
buffer.front().writeGCode(gcode);
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
}
buffer.pop_front();
}
@@ -30,52 +27,51 @@ void LayerPlanBuffer::flush()
void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp)
{
double acc_time = 0.0;
for (unsigned int path_idx = extruder_plan_before.paths.size() - 1; int(path_idx) != -1 ; path_idx--)
for (unsigned int path_idx = 0; path_idx < extruder_plan_before.paths.size(); path_idx++)
{
GCodePath& path = extruder_plan_before.paths[path_idx];
const double time_this_path = path.estimates.getTotalTime();
acc_time += time_this_path;
acc_time += path.estimates.getTotalTime();
if (acc_time > time_after_extruder_plan_start)
{
const double time_before_path_end = acc_time - time_after_extruder_plan_start;
extruder_plan_before.insertCommand(path_idx, extruder, temp, false, time_this_path - time_before_path_end);
// logError("Inserting %f\t seconds too early!\n", acc_time - time_after_extruder_plan_start);
extruder_plan_before.insertCommand(path_idx, extruder, temp, false, acc_time - time_after_extruder_plan_start);
return;
}
}
extruder_plan_before.insertCommand(0, extruder, temp, false); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
extruder_plan_before.insertCommand(extruder_plan_before.paths.size(), extruder, temp, false); // insert at end of extruder plan if time_after_extruder_plan_start > extruder_plan.time
// = special insert after all extruder plans
}
Preheat::WarmUpResult LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
double LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx)
{
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
ExtruderPlan& extruder_plan = layers[layer_plan_idx]->extruder_plans[extruder_plan_idx];
int extruder = extruder_plan.extruder;
double required_temp = extruder_plan.required_temp;
unsigned int extruder_plan_before_idx = extruder_plan_idx - 1;
bool first_it = true;
double in_between_time = 0.0;
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
{ // find a previous extruder plan where the same extruder is used to see what time this extruder wasn't used
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_before_idx];
if (extruder_plan.extruder == extruder)
for (unsigned int layer_idx = layer_plan_idx; int(layer_idx) >= 0; layer_idx--)
{
GCodePlanner& layer = *layers[layer_idx];
if (!first_it)
{
Preheat::WarmUpResult warm_up = preheat_config.timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(in_between_time, extruder, required_temp);
warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time);
return warm_up;
extruder_plan_before_idx = layer.extruder_plans.size() - 1;
}
in_between_time += extruder_plan.estimates.getTotalTime();
for ( ; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
{
ExtruderPlan& extruder_plan = layer.extruder_plans[extruder_plan_before_idx];
if (extruder_plan.extruder == extruder)
{
return preheat_config.timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(in_between_time, extruder, required_temp);
}
in_between_time += extruder_plan.estimates.getTotalTime();
}
first_it = false;
}
// The last extruder plan with the same extruder falls outside of the buffer
// assume the nozzle has cooled down to strandby temperature already.
Preheat::WarmUpResult warm_up;
warm_up.total_time_window = in_between_time;
warm_up.lowest_temperature = preheat_config.getStandbyTemp(extruder);
warm_up.heating_time = preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(warm_up.lowest_temperature, extruder, required_temp, false);
if (warm_up.heating_time > in_between_time)
{
warm_up.heating_time = in_between_time;
warm_up.lowest_temperature = in_between_time / preheat_config.getTimeToHeatup1Degree(extruder);
}
warm_up.heating_time = warm_up.heating_time + extra_preheat_time;
return warm_up;
return preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(preheat_config.getStandbyTemp(extruder), extruder, required_temp, false);
}
@@ -83,81 +79,95 @@ void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_ex
{
// time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
double time_before_extruder_plan_end = 0.5 * preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(prev_extruder_plan.required_temp, extruder, required_temp, true);
time_before_extruder_plan_end = std::min(prev_extruder_plan.estimates.getTotalTime(), time_before_extruder_plan_end);
insertPreheatCommand(prev_extruder_plan, time_before_extruder_plan_end, extruder, required_temp);
}
void LayerPlanBuffer::handleStandbyTemp(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx, double standby_temp)
{
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
int extruder = extruder_plan.extruder;
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 2; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
double time_after_extruder_plan_start = prev_extruder_plan.estimates.getTotalTime() - time_before_extruder_plan_end;
if (time_after_extruder_plan_start < 0)
{
if (extruder_plans[extruder_plan_before_idx]->extruder == extruder)
{
extruder_plans[extruder_plan_before_idx + 1]->prev_extruder_standby_temp = standby_temp;
return;
}
time_after_extruder_plan_start = 0; // don't override the extruder plan with same extruder of the previous layer
}
logWarning("Warning: Couldn't find previous extruder plan so as to set the standby temperature. Inserting temp command in earliest available layer.\n");
ExtruderPlan& earliest_extruder_plan = *extruder_plans[0];
constexpr bool wait = false;
earliest_extruder_plan.insertCommand(0, extruder, standby_temp, wait);
insertPreheatCommand(prev_extruder_plan, time_after_extruder_plan_start, extruder, required_temp);
}
void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx)
{
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
ExtruderPlan& extruder_plan = layers[layer_plan_idx]->extruder_plans[extruder_plan_idx];
int extruder = extruder_plan.extruder;
double required_temp = extruder_plan.required_temp;
Preheat::WarmUpResult heating_time_and_from_temp = timeBeforeExtruderPlanToInsert(extruder_plans, extruder_plan_idx);
if (heating_time_and_from_temp.total_time_window < preheat_config.getMinimalTimeWindow(extruder))
extruder_plan.insertCommand(0, extruder, required_temp, true); // just after the extruder switch, wait for the destination temperature to be reached
double time_before_extruder_plan_to_insert = timeBeforeExtruderPlanToInsert(layers, layer_plan_idx, extruder_plan_idx);
unsigned int extruder_plan_before_idx = extruder_plan_idx - 1;
bool first_it = true; // Whether it's the first iteration of the for loop below
for (unsigned int layer_idx = layer_plan_idx; int(layer_idx) >= 0; layer_idx--)
{
handleStandbyTemp(extruder_plans, extruder_plan_idx, required_temp);
return; // don't insert preheat command and just stay on printing temperature
}
else
{
handleStandbyTemp(extruder_plans, extruder_plan_idx, heating_time_and_from_temp.lowest_temperature);
}
double time_before_extruder_plan_to_insert = heating_time_and_from_temp.heating_time;
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
{
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
assert (extruder_plan_before.extruder != extruder);
double time_here = extruder_plan_before.estimates.getTotalTime();
if (time_here >= time_before_extruder_plan_to_insert)
GCodePlanner& layer = *layers[layer_idx];
if (!first_it)
{
insertPreheatCommand(extruder_plan_before, time_before_extruder_plan_to_insert, extruder, required_temp);
return;
extruder_plan_before_idx = layer.extruder_plans.size() - 1;
}
time_before_extruder_plan_to_insert -= time_here;
for ( ; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
{
ExtruderPlan& extruder_plan_before = layer.extruder_plans[extruder_plan_before_idx];
assert (extruder_plan_before.extruder != extruder);
double time_here = extruder_plan_before.estimates.getTotalTime();
if (time_here > time_before_extruder_plan_to_insert)
{
insertPreheatCommand(extruder_plan_before, time_here - time_before_extruder_plan_to_insert, extruder, required_temp);
return;
}
time_before_extruder_plan_to_insert -= time_here;
}
first_it = false;
}
// time_before_extruder_plan_to_insert falls before all plans in the buffer
extruder_plans[0]->insertCommand(0, extruder, required_temp, false); // insert preheat command at verfy beginning of buffer
ExtruderPlan& first_extruder_plan = layers[0]->extruder_plans[0];
first_extruder_plan.insertCommand(0, extruder, required_temp, false); // insert preheat command at verfy beginning of buffer
}
void LayerPlanBuffer::insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
void LayerPlanBuffer::insertPreheatCommand(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx)
{
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
ExtruderPlan& extruder_plan = layers[layer_plan_idx]->extruder_plans[extruder_plan_idx];
int extruder = extruder_plan.extruder;
double required_temp = extruder_plan.required_temp;
ExtruderPlan* prev_extruder_plan = extruder_plans[extruder_plan_idx - 1];
ExtruderPlan* prev_extruder_plan = nullptr;
if (extruder_plan_idx == 0)
{
if (layer_plan_idx == 0)
{ // the very first extruder plan
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)
{
// extruder_plan.insertCommand(0, extruder, required_temp, true);
// the first used extruder should already be set to the required temp in the start gcode
}
else
{
extruder_plan.insertCommand(0, extruder_idx, preheat_config.getStandbyTemp(extruder_idx), false);
}
}
return;
}
prev_extruder_plan = &layers[layer_plan_idx - 1]->extruder_plans.back();
}
else
{
prev_extruder_plan = &layers[layer_plan_idx]->extruder_plans[extruder_plan_idx - 1];
}
assert(prev_extruder_plan != nullptr);
int prev_extruder = prev_extruder_plan->extruder;
if (prev_extruder != extruder)
{ // set previous extruder to standby temperature
extruder_plan.prev_extruder_standby_temp = preheat_config.getStandbyTemp(prev_extruder);
prev_extruder_plan->insertCommand(prev_extruder_plan->paths.size(), prev_extruder, preheat_config.getStandbyTemp(prev_extruder), false);
}
if (prev_extruder == extruder)
@@ -169,7 +179,7 @@ void LayerPlanBuffer::insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_
}
else
{
insertPreheatCommand_multiExtrusion(extruder_plans, extruder_plan_idx);
insertPreheatCommand_multiExtrusion(layers, layer_plan_idx, extruder_plan_idx);
}
}
@@ -181,20 +191,18 @@ void LayerPlanBuffer::insertPreheatCommands()
buffer.pop_back();
return;
}
std::vector<ExtruderPlan*> extruder_plans;
extruder_plans.reserve(buffer.size() * 2);
std::vector<GCodePlanner*> layers;
layers.reserve(buffer.size());
for (GCodePlanner& layer_plan : buffer)
{
for (ExtruderPlan& extr_plan : layer_plan.extruder_plans)
{
extruder_plans.push_back(&extr_plan);
}
layers.push_back(&layer_plan);
}
unsigned int layer_idx = layers.size() - 1;
// insert commands for all extruder plans on this layer
GCodePlanner& layer_plan = buffer.back();
GCodePlanner& layer_plan = *layers[layer_idx];
for (unsigned int extruder_plan_idx = 0; extruder_plan_idx < layer_plan.extruder_plans.size(); extruder_plan_idx++)
{
ExtruderPlan& extruder_plan = layer_plan.extruder_plans[extruder_plan_idx];
@@ -205,42 +213,10 @@ void LayerPlanBuffer::insertPreheatCommands()
{
continue;
}
double avg_flow = extruder_plan.estimates.getMaterial() / time; // TODO: subtract retracted travel time
extruder_plan.required_temp = preheat_config.getTemp(extruder_plan.extruder, avg_flow);
if (buffer.size() == 1 && extruder_plan_idx == 0)
{ // the very first extruder plan of the current meshgroup
int extruder = extruder_plan.extruder;
for (int extruder_idx = 0; extruder_idx < getSettingAsCount("machine_extruder_count"); extruder_idx++)
{ // set temperature of the first nozzle, turn other nozzles down
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
{
// 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, extruder_plan.required_temp);
}
else
{
gcode.setInitialTemp(extruder_idx, preheat_config.getStandbyTemp(extruder_idx));
}
}
else
{
if (extruder_idx != extruder)
{ // TODO: do we need to do this?
extruder_plan.prev_extruder_standby_temp = preheat_config.getStandbyTemp(extruder_idx);
}
}
}
continue;
}
unsigned int overall_extruder_plan_idx = extruder_plans.size() - layer_plan.extruder_plans.size() + extruder_plan_idx;
insertPreheatCommand(extruder_plans, overall_extruder_plan_idx);
insertPreheatCommand(layers, layer_idx, extruder_plan_idx);
}
}
+18 -30
Ver Arquivo
@@ -3,7 +3,7 @@
#include <list>
#include "settings/settings.h"
#include "settings.h"
#include "commandSocket.h"
#include "gcodeExport.h"
@@ -23,9 +23,7 @@ class LayerPlanBuffer : SettingsMessenger
static constexpr unsigned int buffer_size = 5; // should be as low as possible while still allowing enough time in the buffer to heat up from standby temp to printing temp // TODO: hardcoded value
// this value should be higher than 1, cause otherwise each layer is viewed as the first layer and no temp commands are inserted.
static constexpr const double extra_preheat_time = 1.0; //!< Time to start heating earlier than computed to avoid accummulative discrepancy between actual heating times and computed ones.
public:
std::list<GCodePlanner> buffer; //!< The buffer containing several layer plans (GCodePlanner) before writing them to gcode.
@@ -53,11 +51,9 @@ public:
buffer.emplace_back(constructor_args...);
if (buffer.size() > buffer_size)
{
buffer.front().writeGCode(gcode);
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
}
buffer.pop_front();
}
return buffer.back();
@@ -72,21 +68,22 @@ public:
* Insert the preheat command for @p extruder into @p extruder_plan_before
*
* \param extruder_plan_before An extruder plan before the extruder plan for which the temperature is computed, in which to insert the preheat command
* \param time_before_extruder_plan_end The time before the end of the extruder plan, before which to insert the preheat command
* \param time_after_extruder_plan_start The time after the start of the extruder plan, before which to insert the preheat command
* \param extruder The extruder for which to set the temperature
* \param temp The temperature of the preheat command
*/
void insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_before_extruder_plan_end, int extruder, double temp);
void insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp);
/*!
* Compute the time needed to preheat, based either on the time the extruder has been on standby
* or based on the temp of the previous extruder plan which has the same extruder nr.
*
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to find the preheat time needed
* \return the time needed to preheat and the temperature from which heating starts
* \param layers The layers in the buffer, moved to a vector
* \param layer_plan_idx The index into @p layers in which to find the extruder plan
* \param extruder_plan_idx The index of the extruder plan in the layer corresponding to @p layer_plan_idx for which to find the preheat time needed
* \return the time needed to preheat
*/
Preheat::WarmUpResult timeBeforeExtruderPlanToInsert(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
double timeBeforeExtruderPlanToInsert(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx);
/*!
* For two consecutive extruder plans of the same extruder (so on different layers),
@@ -106,34 +103,25 @@ public:
* and compute at what time the preheat command needs to be inserted.
* Then insert the preheat command in the right extruder plan.
*
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to find the preheat time needed
* \param layers The layers in the buffer, moved to a vector
* \param layer_plan_idx The index into @p layers in which to find the extruder plan
* \param extruder_plan_idx The index of the extruder plan in the layer corresponding to @p layer_plan_idx for which to find the preheat time needed
*/
void insertPreheatCommand_multiExtrusion(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
void insertPreheatCommand_multiExtrusion(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx);
/*!
* Insert the preheat command for the extruder plan corersponding to @p extruder_plan_idx of the layer corresponding to @p layer_plan_idx.
*
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to generate the preheat command
* \param layers The layers of the buffer, moved to a temporary vector (from lower to upper layers)
* \param layer_plan_idx The index of the layer plan for which to generate a preheat command
* \param extruder_plan_idx The index of the extruder plan in the layer corresponding to @p layer_plan_idx for which to generate the preheat command
*/
void insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
void insertPreheatCommand(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx);
/*!
* Insert the preheat commands for the last added layer (unless that layer was empty)
*/
void insertPreheatCommands();
private:
/*!
* Reconfigure the standby temperature during which we didn't print with this extruder.
* Find the previous extruder plan with the same extruder as layers[layer_plan_idx].extruder_plans[extruder_plan_idx]
* Set the prev_extruder_standby_temp in the next extruder plan
*
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans before which to reconfigure the standby temperature
* \param standby_temp The temperature to which to cool down when the extruder is in standby mode.
*/
void handleStandbyTemp(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx, double standby_temp);
};
+2 -2
Ver Arquivo
@@ -141,7 +141,7 @@ bool MergeInfillLines::isConvertible(const Point& a, const Point& b, const Point
(a + b) / 2;
second_middle = (c + d) / 2;
Point dir_vector_perp = turn90CCW(second_middle - first_middle);
Point dir_vector_perp = crossZ(second_middle - first_middle);
int64_t dir_vector_perp_length = vSize(dir_vector_perp); // == dir_vector_length
if (dir_vector_perp_length == 0)
{
@@ -167,7 +167,7 @@ bool MergeInfillLines::isConvertible(const Point& a, const Point& b, const Point
// check whether two lines are adjacent (note: not 'line segments' but 'lines')
Point ac = c - first_middle;
Point infill_vector_perp = turn90CCW(infill_vector);
Point infill_vector_perp = crossZ(infill_vector);
int64_t perp_proj = dot(ac, infill_vector_perp);
int64_t infill_vector_perp_length = vSize(infill_vector_perp);
if (std::abs(std::abs(perp_proj) / infill_vector_perp_length - line_width) > 20) // it should be the case that dot(ac, infill_vector_perp) / |infill_vector_perp| == line_width
-1
Ver Arquivo
@@ -4,7 +4,6 @@
#include "utils/intpoint.h"
#include "gcodeExport.h"
#include "gcodePlanner.h"
#include "GCodePathConfig.h"
namespace cura
{
+5 -118
Ver Arquivo
@@ -28,120 +28,7 @@ void* fgets_(char* ptr, size_t len, FILE* f)
return nullptr;
}
MeshGroup::MeshGroup(SettingsBaseVirtual* settings_base)
: SettingsBase(settings_base)
, 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)
bool loadMeshSTL_ascii(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rt");
char buffer[1024];
@@ -174,7 +61,7 @@ bool loadMeshSTL_ascii(Mesh* mesh, const char* filename, const FMatrix3x3& matri
return true;
}
bool loadMeshSTL_binary(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
bool loadMeshSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rb");
@@ -227,7 +114,7 @@ bool loadMeshSTL_binary(Mesh* mesh, const char* filename, const FMatrix3x3& matr
return true;
}
bool loadMeshSTL(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
bool loadMeshSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "r");
if (f == nullptr)
@@ -281,7 +168,7 @@ bool loadMeshSTL(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
return loadMeshSTL_binary(mesh, filename, matrix);
}
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
{
const char* ext = strrchr(filename, '.');
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
@@ -296,4 +183,4 @@ bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMa
return false;
}
}//namespace cura
}//namespace cura
+101 -14
Ver Arquivo
@@ -20,29 +20,116 @@ class MeshGroup : public SettingsBase, NoCopy
ExtruderTrain* extruders[MAX_EXTRUDERS] = {nullptr};
int extruder_count;
public:
int getExtruderCount();
int getExtruderCount()
{
if (extruder_count == -1)
{
extruder_count = getSettingAsCount("machine_extruder_count");
}
return extruder_count;
}
MeshGroup(SettingsBaseVirtual* settings_base);
MeshGroup(SettingsBaseVirtual* settings_base)
: SettingsBase(settings_base)
, extruder_count(-1)
{}
~MeshGroup();
~MeshGroup()
{
for (unsigned int extruder = 0; extruder < MAX_EXTRUDERS; extruder++)
{
if (extruders[extruder])
{
delete extruders[extruder];
}
}
}
/*!
* Create a new extruder train for the @p extruder_nr, or return the one which already exists.
*/
ExtruderTrain* createExtruderTrain(unsigned int extruder_nr);
ExtruderTrain* getExtruderTrain(unsigned int extruder_nr);
const ExtruderTrain* getExtruderTrain(unsigned int extruder_nr) const;
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;
Point3 min() const; //! minimal corner of bounding box
Point3 max() const; //! maximal corner of bounding box
Point3 min() //! minimal corner of bounding box
{
if (meshes.size() < 1)
{
return Point3(0, 0, 0);
}
Point3 ret = meshes[0].min();
for(unsigned int i=1; i<meshes.size(); i++)
{
Point3 v = meshes[i].min();
ret.x = std::min(ret.x, v.x);
ret.y = std::min(ret.y, v.y);
ret.z = std::min(ret.z, v.z);
}
return ret;
}
Point3 max() //! maximal corner of bounding box
{
if (meshes.size() < 1)
{
return Point3(0, 0, 0);
}
Point3 ret = meshes[0].max();
for(unsigned int i=1; i<meshes.size(); i++)
{
Point3 v = meshes[i].max();
ret.x = std::max(ret.x, v.x);
ret.y = std::max(ret.y, v.y);
ret.z = std::max(ret.z, v.z);
}
return ret;
}
void clear();
void clear()
{
for(Mesh& m : meshes)
{
m.clear();
}
}
void finalize();
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);
}
}
};
/*!
@@ -54,7 +141,7 @@ public:
* \param object_parent_settings (optional) The parent settings object of the new mesh. Defaults to \p meshgroup if none is given.
* \return whether the file could be loaded
*/
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings = nullptr);
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings = nullptr);
}//namespace cura
#endif//MESH_GROUP_H
+8 -49
Ver Arquivo
@@ -33,8 +33,6 @@ class Preheat
double standby_temp; //!< The temperature at which the nozzle rests when it is not printing.
double min_time_window; //!< Minimal time (in seconds) to allow an extruder to cool down and then warm up again.
double material_print_temperature; //!< default print temp (backward compatilibily)
bool flow_dependent_temperature; //!< Whether to make the temperature dependent on flow
@@ -44,16 +42,6 @@ class Preheat
std::vector<Config> config_per_extruder;//!< the nozzle and material temperature settings for each extruder train.
public:
/*!
* The type of result when computing when to start heating up a nozzle before it's going to be used again.
*/
struct WarmUpResult
{
double total_time_window; //!< The total time in which cooling and heating takes place.
double heating_time; //!< The total time needed to heat to the required temperature.
double lowest_temperature; //!< The lower temperature from which heating starts.
};
/*!
* Get the standby temperature of an extruder train
* \param extruder the extruder train for which to get the standby tmep
@@ -63,18 +51,7 @@ public:
{
return config_per_extruder[extruder].standby_temp;
}
/*!
* Get the time it takes to heat up one degree celsius
*
* \param extruder the extruder train for which to get time it takes to heat up one degree celsius
* \return the time it takes to heat up one degree celsius
*/
double getTimeToHeatup1Degree(int extruder)
{
return config_per_extruder[extruder].time_to_heatup_1_degree;
}
/*!
* Set the nozzle and material temperature settings for each extruder train.
*/
@@ -90,9 +67,7 @@ public:
config.time_to_heatup_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed"); // 0.5
config.heatup_cooldown_time_mod_while_printing = 1.0 / extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed"); // 0.1
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature"); // 150
config.min_time_window = extruder_train.getSettingInSeconds("machine_min_cool_heat_time_window");
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature"); // 220
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
@@ -130,17 +105,7 @@ public:
{
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
}
/*!
* Return the minimal time window of a specific extruder for letting an unused extruder cool down and warm up again
* \param extruder The extruder for which to get the minimal time window
* \return the minimal time window of a specific extruder for letting an unused extruder cool down and warm up again
*/
double getMinimalTimeWindow(unsigned int extruder)
{
return config_per_extruder[extruder].min_time_window;
}
/*!
* Decide when to start warming up again after starting to cool down towards the standby temperature.
* Two cases are considered:
@@ -154,27 +119,21 @@ public:
* \param window_time The time window within which the cooldown and heat up must take place.
* \param extruder The extruder used
* \param temp The temperature to which to heat
* \return The time before the end of the @p time_window to insert the preheat command and the temperature from which the heating starts
* \return The time before the end of the @p time_window to insert the preheat command
*/
WarmUpResult timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(double time_window, unsigned int extruder, double temp)
double timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(double time_window, unsigned int extruder, double temp)
{
WarmUpResult result;
const Config& config = config_per_extruder[extruder];
result.total_time_window = time_window;
double time_ratio_cooldown_heatup = config.time_to_cooldown_1_degree / config.time_to_heatup_1_degree;
double time_ratio_cooldown_heatup = config_per_extruder[extruder].time_to_cooldown_1_degree / config_per_extruder[extruder].time_to_heatup_1_degree;
double time_to_heat_from_standby_to_print_temp = timeToHeatFromStandbyToPrintTemp(extruder, temp);
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
if (time_needed_to_reach_standby_temp < time_window)
{
result.heating_time = time_to_heat_from_standby_to_print_temp;
result.lowest_temperature = config.standby_temp;
return time_to_heat_from_standby_to_print_temp;
}
else
{
result.heating_time = time_window * config.time_to_heatup_1_degree / (config.time_to_cooldown_1_degree + config.time_to_heatup_1_degree);
result.lowest_temperature = std::max(config.standby_temp, temp - result.heating_time / config.time_to_heatup_1_degree);
return time_window * config_per_extruder[extruder].time_to_heatup_1_degree / (config_per_extruder[extruder].time_to_cooldown_1_degree + config_per_extruder[extruder].time_to_heatup_1_degree);
}
return result;
}
/*!
* Calculate time needed to warm up the nozzle from a given temp to a given temp.
+19 -13
Ver Arquivo
@@ -23,12 +23,12 @@ void PrimeTower::initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>
for (int extr = 0; extr < extruder_count; extr++)
{
config_per_extruder.emplace_back(PrintFeatureType::Support);// so that visualization in the old Cura still works (TODO)
config_per_extruder.emplace_back(&retraction_config_per_extruder[extr], PrintFeatureType::Support);// so that visualization in the old Cura still works (TODO)
}
for (int extr = 0; extr < extruder_count; extr++)
{
ExtruderTrain* train = meshgroup->getExtruderTrain(extr);
config_per_extruder[extr].init(train->getSettingInMillimetersPerSecond("speed_prime_tower"), train->getSettingInMillimetersPerSecond("acceleration_prime_tower"), train->getSettingInMillimetersPerSecond("jerk_prime_tower"), train->getSettingInMicrons("prime_tower_line_width"), train->getSettingInPercentage("prime_tower_flow"));
config_per_extruder[extr].init(train->getSettingInMillimetersPerSecond("speed_prime_tower"), train->getSettingInMicrons("prime_tower_line_width"), train->getSettingInPercentage("prime_tower_flow"));
}
}
@@ -61,9 +61,9 @@ void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
std::max( max_object_height_per_extruder[extr_nr]
, mesh.layer_nr_max_filled_layer );
}
int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
max_object_height_per_extruder[support_infill_extruder_nr] =
std::max( max_object_height_per_extruder[support_infill_extruder_nr]
int support_extruder_nr = storage.getSettingAsIndex("support_extruder_nr"); // TODO: support extruder should be configurable per object
max_object_height_per_extruder[support_extruder_nr] =
std::max( max_object_height_per_extruder[support_extruder_nr]
, storage.support.layer_nr_max_filled_layer );
int support_roof_extruder_nr = storage.getSettingAsIndex("support_roof_extruder_nr"); // TODO: support roof extruder should be configurable per object
max_object_height_per_extruder[support_roof_extruder_nr] =
@@ -104,7 +104,7 @@ void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
{
PolygonRef p = storage.primeTower.ground_poly.newPoly();
int tower_size = storage.getSettingInMicrons("prime_tower_size");
int tower_distance = 0;
int tower_distance = 0; //storage.getSettingInMicrons("prime_tower_distance");
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
p.add(Point(x + tower_distance, y + tower_distance));
@@ -117,7 +117,9 @@ 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.getSettingBoolean("prime_tower_enable"))
if (storage.max_object_height_second_to_last_extruder >= 0
// && storage.getSettingInMicrons("prime_tower_distance") > 0
&& storage.getSettingInMicrons("prime_tower_size") > 0)
{
generatePaths3(storage);
}
@@ -125,11 +127,11 @@ 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.getSettingBoolean("prime_tower_enable"))
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_distance") > 0 && storage.getSettingInMicrons("prime_tower_size") > 0)
{
PolygonRef p = storage.primeTower.ground_poly.newPoly();
int tower_size = storage.getSettingInMicrons("prime_tower_size");
int tower_distance = 0;
int tower_distance = 0; //storage.getSettingInMicrons("prime_tower_distance");
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
p.add(Point(x + tower_distance, y + tower_distance));
@@ -168,7 +170,7 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
{
int n_patterns = 2; // alternating patterns between layers
int infill_overlap = 60; // so that it can't be zero; EDIT: wtf?
double infill_overlap = 15; // so that it can't be zero; EDIT: wtf?
generateGroundpoly(storage);
@@ -180,12 +182,14 @@ 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, line_width, line_distance, infill_overlap, fill_angle);
infill_comp.generate(result_polygons, result_lines);
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);
}
}
}
@@ -194,7 +198,9 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
{
if (!( storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0) )
if (!( storage.max_object_height_second_to_last_extruder >= 0
// && storage.getSettingInMicrons("prime_tower_distance") > 0
&& storage.getSettingInMicrons("prime_tower_size") > 0) )
{
return;
}
+1 -1
Ver Arquivo
@@ -1,7 +1,7 @@
#ifndef PRIME_TOWER_H
#define PRIME_TOWER_H
#include "GCodePathConfig.h"
#include "gcodeExport.h" // GCodePathConfig
#include "MeshGroup.h"
#include "utils/polygon.h" // Polygons
+14 -11
Ver Arquivo
@@ -1,28 +1,30 @@
/** 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, // 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
0.0,
5.269,
1.533,
22.953,
51.009,
48.858,
154.62,
0.1
};
std::string Progress::names [] =
{
"start",
"slice",
"layerparts",
"inset+skin",
"inset",
"support",
"skin",
"export",
"process"
};
@@ -37,8 +39,9 @@ const Progress::Stage Progress::stages[] =
Progress::Stage::START,
Progress::Stage::SLICING,
Progress::Stage::PARTS,
Progress::Stage::INSET_SKIN,
Progress::Stage::INSET,
Progress::Stage::SUPPORT,
Progress::Stage::SKIN,
Progress::Stage::EXPORT,
Progress::Stage::FINISH
};
+7 -6
Ver Arquivo
@@ -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 7
#define N_PROGRESS_STAGES 8
/*!
* Class for handling the progress bar and the progress logging.
@@ -30,10 +30,11 @@ public:
START = 0,
SLICING = 1,
PARTS = 2,
INSET_SKIN = 3,
INSET = 3,
SUPPORT = 4,
EXPORT = 5,
FINISH = 6
SKIN = 5,
EXPORT = 6,
FINISH = 7
};
private:
static double times [N_PROGRESS_STAGES]; //!< Time estimates per stage
-28
Ver Arquivo
@@ -1,28 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef RETRACTION_CONFIG_H
#define RETRACTION_CONFIG_H
namespace cura
{
/*!
* The retraction configuration used in the GCodePathConfig of each feature (and the travel config)
*/
class RetractionConfig
{
public:
double distance; //!< The distance retracted (in mm)
double speed; //!< The speed with which to retract (in mm/s)
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; //!< 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
};
}//namespace cura
#endif // RETRACTION_CONFIG_H
-79
Ver Arquivo
@@ -1,79 +0,0 @@
/** 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
-69
Ver Arquivo
@@ -1,69 +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
{
/*!
* 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
+11 -12
Ver Arquivo
@@ -4,7 +4,7 @@
#include <fstream> // debug IO
#include <unistd.h>
#include "progress/Progress.h"
#include "Progress.h"
#include "weaveDataStorage.h"
#include "PrintFeature.h"
@@ -35,7 +35,7 @@ void Weaver::weave(MeshGroup* meshgroup)
{
Polygons parts;
for (cura::Slicer* slicer : slicerList)
parts.add(slicer->layers[starting_layer_idx].polygons);
parts.add(slicer->layers[starting_layer_idx].polygonList);
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].polygons);
wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygonList);
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_SKIN, nullptr);
Progress::messageProgressStage(Progress::Stage::INSET, nullptr);
for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++)
{
Progress::messageProgress(Progress::Stage::INSET_SKIN, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
Progress::messageProgress(Progress::Stage::INSET, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
Polygons parts1;
for (cura::Slicer* slicer : slicerList)
parts1.add(slicer->layers[layer_idx].polygons);
parts1.add(slicer->layers[layer_idx].polygonList);
Polygons chainified;
@@ -86,9 +86,8 @@ void Weaver::weave(MeshGroup* meshgroup)
chainify_polygons(parts1, starting_point_in_layer, chainified, false);
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, layer_idx - starting_layer_idx, chainified, 1);
}
if (chainified.size() > 0)
{
if (starting_z == -1) starting_z = slicerList[0]->layers[layer_idx-1].z;
@@ -109,10 +108,10 @@ void Weaver::weave(MeshGroup* meshgroup)
{
Polygons* lower_top_parts = &wireFrame.bottom_outline;
Progress::messageProgressStage(Progress::Stage::SUPPORT, nullptr);
Progress::messageProgressStage(Progress::Stage::SKIN, nullptr);
for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++)
{
Progress::messageProgress(Progress::Stage::SUPPORT, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
Progress::messageProgress(Progress::Stage::SKIN, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
WeaveLayer& layer = wireFrame.layers[layer_idx];
@@ -403,9 +402,9 @@ void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons&
bool found = true;
int idx = 0;
for (Point upper_point = upperPart[closestInPoly.point_idx]; found; upper_point = next_upper.location)
for (Point upper_point = upperPart[closestInPoly.pos]; found; upper_point = next_upper.location)
{
found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.point_idx, next_upper);
found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.pos, next_upper);
if (!found)
+1 -1
Ver Arquivo
@@ -3,7 +3,7 @@
#include "weaveDataStorage.h"
#include "commandSocket.h"
#include "settings/settings.h"
#include "settings.h"
#include "MeshGroup.h"
#include "slicer.h"
+22 -21
Ver Arquivo
@@ -4,7 +4,7 @@
#include <fstream> // debug IO
#include "weaveDataStorage.h"
#include "progress/Progress.h"
#include "Progress.h"
#include "pathOrderOptimizer.h" // for skirt
@@ -17,8 +17,6 @@ void Wireframe2gcode::writeGCode()
gcode.preSetup(wireFrame.meshgroup);
gcode.setInitialTemps(wireFrame.meshgroup);
if (CommandSocket::getInstance())
CommandSocket::getInstance()->beginGCode();
@@ -39,7 +37,7 @@ void Wireframe2gcode::writeGCode()
unsigned int total_layers = wireFrame.layers.size();
gcode.writeLayerComment(0);
gcode.writeTypeComment(PrintFeatureType::Skirt);
gcode.writeTypeComment("SKIRT");
gcode.setZ(initial_layer_thickness);
@@ -98,7 +96,7 @@ void Wireframe2gcode::writeGCode()
if (part.connection.segments.size() == 0) continue;
gcode.writeTypeComment(PrintFeatureType::Support); // connection
gcode.writeTypeComment("SUPPORT"); // connection
{
if (vSize2(gcode.getPositionXY() - part.connection.from) > connectionHeight)
{
@@ -114,7 +112,7 @@ void Wireframe2gcode::writeGCode()
gcode.writeTypeComment(PrintFeatureType::OuterWall); // top
gcode.writeTypeComment("WALL-OUTER"); // top
{
for (unsigned int segment_idx = 0; segment_idx < part.connection.segments.size(); segment_idx++)
{
@@ -241,7 +239,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 = getSettingInMillimeters("retraction_extrusion_window");
retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
double top_retract_pause = 2.0;
@@ -406,7 +404,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
{
// bottom:
gcode.writeTypeComment(PrintFeatureType::Infill);
gcode.writeTypeComment("FILL");
for (unsigned int inset_idx = 0; inset_idx < infill_insets.size(); inset_idx++)
{
WeaveRoofPart& inset = infill_insets[inset_idx];
@@ -417,7 +415,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
WeaveConnectionPart& inset_part = inset.connections[inset_part_nr];
std::vector<WeaveConnectionSegment>& segments = inset_part.connection.segments;
gcode.writeTypeComment(PrintFeatureType::Support); // connection
gcode.writeTypeComment("SUPPORT"); // connection
if (segments.size() == 0) continue;
Point3 first_extrusion_from = inset_part.connection.from;
unsigned int first_segment_idx;
@@ -433,7 +431,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
connectionHandler(*this, inset, inset_part, segment_idx);
}
gcode.writeTypeComment(PrintFeatureType::InnerWall); // top
gcode.writeTypeComment("WALL-INNER"); // top
for (unsigned int segment_idx = 0; segment_idx < segments.size(); segment_idx++)
{
WeaveConnectionSegment& segment = segments[segment_idx];
@@ -447,7 +445,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
}
gcode.writeTypeComment(PrintFeatureType::OuterWall); // outer perimeter of the flat parts
gcode.writeTypeComment("WALL-OUTER"); // outer perimeter of the flat parts
for (PolygonRef poly : roof_outlines)
{
writeMoveWithRetract(poly[poly.size() - 1]);
@@ -536,21 +534,24 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
roof_outer_delay = getSettingInSeconds("wireframe_roof_outer_delay");
standard_retraction_config.distance = getSettingInMillimeters("retraction_amount");
standard_retraction_config.distance = INT2MM(getSettingInMicrons("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 = getSettingInMillimeters("retraction_extrusion_window");
standard_retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
standard_retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
}
void Wireframe2gcode::processStartingCode()
{
if (!CommandSocket::isInstantiated())
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
{
gcode.writeCode(gcode.getFileHeader().c_str());
if (!CommandSocket::isInstantiated())
{
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
}
}
else
{
@@ -599,14 +600,14 @@ void Wireframe2gcode::processSkirt()
order.addPolygons(skirt);
order.optimize();
for (unsigned int poly_order_idx = 0; poly_order_idx < skirt.size(); poly_order_idx++)
for (unsigned int poly_idx = 0; poly_idx < skirt.size(); poly_idx++)
{
unsigned int poly_idx = order.polyOrder[poly_order_idx];
PolygonRef poly = skirt[poly_idx];
gcode.writeMove(poly[order.polyStart[poly_idx]], getSettingInMillimetersPerSecond("speed_travel"), 0);
unsigned int actual_poly_idx = order.polyOrder[poly_idx];
PolygonRef poly = skirt[actual_poly_idx];
gcode.writeMove(poly[order.polyStart[actual_poly_idx]], getSettingInMillimetersPerSecond("speed_travel"), 0);
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
Point& p = poly[(point_idx + order.polyStart[poly_idx] + 1) % poly.size()];
Point& p = poly[(point_idx + order.polyStart[actual_poly_idx] + 1) % poly.size()];
gcode.writeMove(p, getSettingInMillimetersPerSecond("skirt_speed"), getSettingInMillimetersPerSecond("skirt_line_width"));
}
}
@@ -615,7 +616,7 @@ void Wireframe2gcode::processSkirt()
void Wireframe2gcode::finalize()
{
gcode.finalize(getSettingString("machine_end_gcode").c_str());
gcode.finalize(getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
for(int e=0; e<getSettingAsCount("machine_extruder_count"); e++)
gcode.writeTemperatureCommand(e, 0, false);
}
+1 -1
Ver Arquivo
@@ -8,7 +8,7 @@
#include "weaveDataStorage.h"
#include "commandSocket.h"
#include "settings/settings.h"
#include "settings.h"
#include "MeshGroup.h"
#include "slicer.h"
+391
Ver Arquivo
@@ -0,0 +1,391 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "comb.h"
#include <algorithm>
#include "utils/polygonUtils.h"
#include "sliceDataStorage.h"
#include "utils/SVG.h"
namespace cura {
// boundary_outside is only computed when it's needed!
Polygons* Comb::getBoundaryOutside()
{
if (!boundary_outside)
{
boundary_outside = new Polygons();
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
}
return boundary_outside;
}
Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
: storage(storage)
, layer_nr(layer_nr)
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
, max_moveInside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
, offset_from_outlines_outside(travel_avoid_distance)
, avoid_other_parts(travel_avoid_other_parts)
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
, boundary_inside( comb_boundary_inside )
, boundary_outside(nullptr)
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
{
}
Comb::~Comb()
{
if (boundary_outside)
delete boundary_outside;
}
bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored)
{
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored))
{
return true;
}
//Move start and end point inside the comb boundary
unsigned int start_inside_poly = NO_INDEX;
if (startInside)
{
start_inside_poly = PolygonUtils::moveInside(boundary_inside, startPoint, offset_extra_start_end, max_moveInside_distance2);
if (!boundary_inside.inside(start_inside_poly) || start_inside_poly == NO_INDEX)
{
if (start_inside_poly != NO_INDEX)
{ // if not yet inside because of overshoot, try again
start_inside_poly = PolygonUtils::moveInside(boundary_inside, startPoint, offset_extra_start_end, max_moveInside_distance2);
}
if (start_inside_poly == NO_INDEX) //If we fail to move the point inside the comb boundary we need to retract.
{
startInside = false;
}
}
}
unsigned int end_inside_poly = NO_INDEX;
if (endInside)
{
end_inside_poly = PolygonUtils::moveInside(boundary_inside, endPoint, offset_extra_start_end, max_moveInside_distance2);
if (!boundary_inside.inside(endPoint) || end_inside_poly == NO_INDEX)
{
if (end_inside_poly != NO_INDEX)
{ // if not yet inside because of overshoot, try again
end_inside_poly = PolygonUtils::moveInside(boundary_inside, endPoint, offset_extra_start_end, max_moveInside_distance2);
}
if (end_inside_poly == NO_INDEX) //If we fail to move the point inside the comb boundary we need to retract.
{
endInside = false;
}
}
}
unsigned int start_part_boundary_poly_idx;
unsigned int end_part_boundary_poly_idx;
unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx);
unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx);
if (startInside && endInside && start_part_idx == end_part_idx)
{ // normal combing within part
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
combPaths.emplace_back();
LinePolygonsCrossings::comb(part, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
return true;
}
else
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
Point middle_from;
Point middle_to;
Point inside_middle_from;
Point inside_middle_to;
if (startInside && endInside)
{
ClosestPolygonPoint middle_from_cp = PolygonUtils::findClosest(endPoint, boundary_inside[start_part_boundary_poly_idx]);
ClosestPolygonPoint middle_to_cp = PolygonUtils::findClosest(middle_from_cp.location, boundary_inside[end_part_boundary_poly_idx]);
// walkToNearestSmallestConnection(middle_from_cp, middle_to_cp); // TODO: perform this optimization?
middle_from = middle_from_cp.location;
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);
}
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);
}
// throught air from boundary to boundary
if (avoid_other_parts)
{
Polygons& middle = *getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
Point from_outside = middle_from;
if (startInside || middle.inside(from_outside, true))
{ // move outside
PolygonUtils::moveInside(middle, from_outside, -offset_extra_start_end, max_moveInside_distance2);
}
Point to_outside = middle_to;
if (endInside || middle.inside(to_outside, true))
{ // move outside
PolygonUtils::moveInside(middle, to_outside, -offset_extra_start_end, max_moveInside_distance2);
}
combPaths.emplace_back();
combPaths.back().throughAir = true;
if ( vSize(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);
}
else
{
LinePolygonsCrossings::comb(middle, from_outside, to_outside, 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.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);
}
if (endInside)
{
// boundary to end
PolygonsPart part_end = partsView_inside.assemblePart(end_part_idx); // comb through end part only
combPaths.emplace_back();
LinePolygonsCrossings::comb(part_end, inside_middle_to, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
}
return true;
}
}
void LinePolygonsCrossings::calcScanlineCrossings()
{
min_crossing_idx = NO_INDEX;
max_crossing_idx = NO_INDEX;
for(unsigned int poly_idx = 0; poly_idx < boundary.size(); poly_idx++)
{
PolyCrossings minMax(poly_idx);
PolygonRef poly = boundary[poly_idx];
Point p0 = transformation_matrix.apply(poly[poly.size() - 1]);
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
Point p1 = transformation_matrix.apply(poly[point_idx]);
if((p0.Y >= transformed_startPoint.Y && p1.Y <= transformed_startPoint.Y) || (p1.Y >= transformed_startPoint.Y && p0.Y <= transformed_startPoint.Y))
{
if(p1.Y == p0.Y) //Line segment is parallel with the scanline. That means that both endpoints lie on the scanline, so they will have intersected with the adjacent line.
{
p0 = p1;
continue;
}
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
if (x >= transformed_startPoint.X && x <= transformed_endPoint.X)
{
if(x < minMax.min.x) //For the leftmost intersection, move x left to stay outside of the border.
//Note: The actual distance from the intersection to the border is almost always less than dist_to_move_boundary_point_outside, since it only moves along the direction of the scanline.
{
minMax.min.x = x;
minMax.min.point_idx = point_idx;
}
if(x > minMax.max.x) //For the rightmost intersection, move x right to stay outside of the border.
{
minMax.max.x = x;
minMax.max.point_idx = point_idx;
}
}
}
p0 = p1;
}
if (minMax.min.point_idx != NO_INDEX)
{ // then also max.point_idx != -1
if (min_crossing_idx == NO_INDEX || minMax.min.x < crossings[min_crossing_idx].min.x) { min_crossing_idx = crossings.size(); }
if (max_crossing_idx == NO_INDEX || minMax.max.x > crossings[max_crossing_idx].max.x) { max_crossing_idx = crossings.size(); }
crossings.push_back(minMax);
}
}
}
bool LinePolygonsCrossings::lineSegmentCollidesWithBoundary()
{
Point diff = endPoint - startPoint;
transformation_matrix = PointMatrix(diff);
transformed_startPoint = transformation_matrix.apply(startPoint);
transformed_endPoint = transformation_matrix.apply(endPoint);
for(PolygonRef poly : boundary)
{
Point p0 = transformation_matrix.apply(poly.back());
for(Point p1_ : poly)
{
Point p1 = transformation_matrix.apply(p1_);
if ((p0.Y > transformed_startPoint.Y && p1.Y < transformed_startPoint.Y) || (p1.Y > transformed_startPoint.Y && p0.Y < transformed_startPoint.Y))
{
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
return true;
}
p0 = p1;
}
}
return false;
}
void LinePolygonsCrossings::getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored)
{
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored) || !lineSegmentCollidesWithBoundary())
{
//We're not crossing any boundaries. So skip the comb generation.
combPath.push_back(startPoint);
combPath.push_back(endPoint);
return;
}
calcScanlineCrossings();
CombPath basicPath;
getBasicCombingPath(basicPath);
optimizePath(basicPath, combPath);
}
void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
{
for (PolyCrossings* crossing = getNextPolygonAlongScanline(transformed_startPoint.X)
; crossing != nullptr
; crossing = getNextPolygonAlongScanline(crossing->max.x))
{
getBasicCombingPath(*crossing, combPath);
}
combPath.push_back(endPoint);
}
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
{
PolygonRef poly = boundary[polyCrossings.poly_idx];
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x + 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
for(unsigned int point_idx = polyCrossings.min.point_idx
; point_idx != polyCrossings.max.point_idx
; point_idx = (point_idx < poly.size() - 1) ? (point_idx + 1) : (0))
{
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
}
}
else
{ // follow the path in the opposite direction of the winding order of the boundary polygon
unsigned int min_idx = (polyCrossings.min.point_idx == 0)? poly.size() - 1: polyCrossings.min.point_idx - 1;
unsigned int max_idx = (polyCrossings.max.point_idx == 0)? poly.size() - 1: polyCrossings.max.point_idx - 1;
for(unsigned int point_idx = min_idx; point_idx != max_idx; point_idx = (point_idx > 0) ? (point_idx - 1) : (poly.size() - 1))
{
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
}
}
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x - dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
}
LinePolygonsCrossings::PolyCrossings* LinePolygonsCrossings::getNextPolygonAlongScanline(int64_t x)
{
PolyCrossings* ret = nullptr;
for(PolyCrossings& crossing : crossings)
{
if (crossing.min.x > x && (ret == nullptr || crossing.min.x < ret->min.x) )
{
ret = &crossing;
}
}
return ret;
}
bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimized_comb_path)
{
optimized_comb_path.push_back(startPoint);
for(unsigned int point_idx = 1; point_idx<comb_path.size(); point_idx++)
{
if(comb_path[point_idx] == comb_path[point_idx - 1]) //Two points are the same. Skip the second.
{
continue;
}
Point& current_point = optimized_comb_path.back();
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
{
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx - 1]))
{
comb_path.cross_boundary = true;
}
optimized_comb_path.push_back(comb_path[point_idx - 1]);
}
else
{
// : dont add the newest point
// TODO: add the below extra optimization? (+/- 7% extra computation time, +/- 2% faster print for Dual_extrusion_support_generation.stl)
while (optimized_comb_path.size() > 1)
{
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
{
break;
}
else
{
optimized_comb_path.pop_back();
}
}
}
}
return true;
}
}//namespace cura
@@ -1,13 +1,20 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
#define PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
#ifndef COMB_H
#define COMB_H
#include "../utils/polygon.h"
#include "CombPath.h"
#include "utils/polygon.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
{
};
/*!
* Class for generating a combing move action from point a to point b and avoiding collision with other parts when moving through air.
@@ -53,7 +60,6 @@ private:
unsigned int poly_idx; //!< The index of the polygon which crosses the scanline
Crossing min; //!< The point where the polygon first crosses the scanline.
Crossing max; //!< The point where the polygon last crosses the scanline.
int n_crossings; //!< The number of times the polygon crossed the scanline.
/*!
* Create a PolyCrossings with minimal initialization. PolyCrossings::min and PolyCrossings::max are not yet computed.
* \param poly_idx The index of the polygon in LinePolygonsCrossings::boundary
@@ -61,7 +67,6 @@ private:
PolyCrossings(unsigned int poly_idx)
: poly_idx(poly_idx)
, min(INT64_MAX, NO_INDEX), max(INT64_MIN, NO_INDEX)
, n_crossings(0)
{
}
};
@@ -86,8 +91,8 @@ private:
int64_t dist_to_move_boundary_point_outside; //!< The distance used to move outside or inside so that a boundary point doesn't intersect with the boundary anymore. Neccesary due to computational rounding problems. Use negative value for insicde combing.
PointMatrix transformation_matrix; //!< The transformation which rotates everything such that the scanline is aligned with the x-axis.
Point transformed_startPoint; //!< The LinePolygonsCrossings::startPoint as transformed by Comb::transformation_matrix such that it has (roughly) the same Y as transformed_endPoint
Point transformed_endPoint; //!< The LinePolygonsCrossings::endPoint as transformed by Comb::transformation_matrix such that it has (roughly) the same Y as transformed_startPoint
Point transformed_startPoint; //!< The LinePolygonsCrossings::startPoint as transformed by Comb::transformation_matrix
Point transformed_endPoint; //!< The LinePolygonsCrossings::endPoint as transformed by Comb::transformation_matrix
/*!
@@ -100,19 +105,15 @@ private:
/*!
* Calculate Comb::crossings, Comb::min_crossing_idx and Comb::max_crossing_idx.
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
* \return Whether combing succeeded, i.e. when fail_on_unavoidable_obstacles: we didn't cross any gaps/other parts
*/
bool calcScanlineCrossings(bool fail_on_unavoidable_obstacles);
void calcScanlineCrossings();
/*!
* Get the basic combing path and optimize it.
*
* \param combPath Output parameter: the points along the combing path.
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
* \return Whether combing succeeded, i.e. we didn't cross any gaps/other parts
*/
bool getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles);
void getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored = MM2INT(1.5));
/*!
* Get the basic combing path, without shortcuts. The path goes straight toward the endPoint and follows the boundary when it hits it, until it passes the scanline again.
@@ -176,16 +177,81 @@ public:
* \param startPoint From where to start the combing move.
* \param endPoint Where to end the combing move.
* \param combPath Output parameter: the combing path generated.
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
* \return Whether combing succeeded, i.e. we didn't cross any gaps/other parts
*/
static bool comb(Polygons& boundary, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
static void comb(Polygons& boundary, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored = MM2INT(1.5))
{
LinePolygonsCrossings linePolygonsCrossings(boundary, startPoint, endPoint, dist_to_move_boundary_point_outside);
return linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored);
};
};
class SliceDataStorage;
/*!
* Class for generating a full combing actions from a travel move from a start point to an end point.
* A single Comb object is used for each layer.
*
* Comb::calc is the main function of this class.
*
* Typical output: A combing path to the boundary of the polygon + a move through air avoiding other parts in the layer + a combing path from the boundary of the ending polygon to the end point.
* Each of these three is a CombPath; the first and last are within Comb::boundary_inside while the middle is outside of Comb::boundary_outside.
* Between these there is a little gap where the nozzle crosses the boundary of an object approximately perpendicular to its boundary.
*
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
* because generally there is only one outside part; encapsulated holes occur less often.
*/
class Comb
{
friend class LinePolygonsCrossings;
private:
SliceDataStorage& storage; //!< The storage from which to compute the outside boundary, when needed.
int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
int64_t max_moveInside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
static const int64_t max_moveOutside_distance2 = INT64_MAX; //!< Any point which is not inside should be considered outside.
static const int64_t offset_dist_to_get_from_on_the_polygon_to_outside = 40; //!< in order to prevent on-boundary vs crossing boundary confusions (precision thing)
static const int64_t offset_extra_start_end = 100; //!< Distance to move start point and end point toward eachother to extra avoid collision with the boundaries.
bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
Polygons& boundary_inside; //!< The boundary within which to comb.
Polygons* boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
PartsView partsView_inside; //!< Structured indices onto boundary_inside which shows which polygons belong to which part.
/*!
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
*/
Polygons* getBoundaryOutside();
public:
/*!
* Initializes the combing areas for every mesh in the layer (not support)
* \param storage Where the layer polygon data is stored
* \param layer_nr The number of the layer for which to generate the combing areas.
* \param comb_boundary_inside The comb boundary within which to comb within layer parts.
* \param offset_from_outlines The offset from the outline polygon, to create the combing boundary in case there is no second wall.
* \param travel_avoid_other_parts Whether to avoid other layer parts when traveling through air.
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
*/
Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
~Comb();
/*!
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
*
* \param startPoint Where to start moving from
* \param endPoint Where to move to
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
* \param startInside Whether we want to start inside the comb boundary
* \param endInside Whether we want to end up inside the comb boundary
* \return Whether combing has succeeded; otherwise a retraction is needed.
*/
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside = false, bool endInside = false, int64_t max_comb_distance_ignored = MM2INT(1.5));
};
}//namespace cura
#endif//PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
#endif//COMB_H
+102 -165
Ver Arquivo
@@ -1,16 +1,12 @@
#include "utils/logoutput.h"
#include "commandSocket.h"
#include "FffProcessor.h"
#include "progress/Progress.h"
#include "Progress.h"
#include <thread>
#include <cinttypes>
#ifdef ARCUS
#include <Arcus/Socket.h>
#include <Arcus/SocketListener.h>
#include <Arcus/Error.h>
#endif
#include <string> // stoi
@@ -18,10 +14,7 @@
#include <windows.h>
#endif
#include "settings/SettingRegistry.h" // loadExtruderJSONsettings
#define DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(x)
// std::cerr << x;
namespace cura {
@@ -32,49 +25,31 @@ namespace cura {
CommandSocket* CommandSocket::instance = nullptr; // instantiate instance
#ifdef ARCUS
class Listener : public Arcus::SocketListener
{
public:
void stateChanged(Arcus::SocketState::SocketState newState) override
{
}
void messageReceived() override
{
}
void error(const Arcus::Error & error) override
{
if (error.getErrorCode() == Arcus::ErrorCode::Debug)
{
log("%s\n", error.toString().c_str());
}
else
{
logError("%s\n", error.toString().c_str());
}
}
};
class CommandSocket::Private
{
public:
Private()
: socket(nullptr)
, object_count(0)
, current_sliced_object(nullptr)
, sliced_objects(0)
, current_layer_count(0)
, current_layer_offset(0)
{ }
std::shared_ptr<cura::proto::Layer> getLayerById(int id);
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;
@@ -82,24 +57,20 @@ public:
// Used for incrementing the current layer in one at a time mode
int current_layer_count;
int current_layer_offset;
// Ids of the sliced objects
std::vector<int64_t> object_ids;
std::string temp_gcode_file;
std::ostringstream gcode_output_stream;
// Print object that olds one or more meshes that need to be sliced.
std::vector< std::shared_ptr<MeshGroup> > objects_to_slice;
std::unordered_map<int, std::shared_ptr<cura::proto::Layer>> sliced_layers;
};
#endif
CommandSocket::CommandSocket()
#ifdef ARCUS
: private_data(new Private)
#endif
{
#ifdef ARCUS
#endif
}
CommandSocket* CommandSocket::getInstance()
@@ -120,30 +91,27 @@ bool CommandSocket::isInstantiated()
void CommandSocket::connect(const std::string& ip, int port)
{
#ifdef ARCUS
private_data->socket = new Arcus::Socket();
private_data->socket->addListener(new Listener());
//private_data->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
private_data->socket->registerMessageType(&cura::proto::Slice::default_instance());
private_data->socket->registerMessageType(&cura::proto::Layer::default_instance());
private_data->socket->registerMessageType(&cura::proto::SlicedObjectList::default_instance());
private_data->socket->registerMessageType(&cura::proto::Progress::default_instance());
private_data->socket->registerMessageType(&cura::proto::GCodeLayer::default_instance());
private_data->socket->registerMessageType(&cura::proto::PrintTimeMaterialEstimates::default_instance());
private_data->socket->registerMessageType(&cura::proto::ObjectPrintTime::default_instance());
private_data->socket->registerMessageType(&cura::proto::SettingList::default_instance());
private_data->socket->registerMessageType(&cura::proto::GCodePrefix::default_instance());
private_data->socket->registerMessageType(&cura::proto::SlicingFinished::default_instance());
private_data->socket->connect(ip, port);
log("Connecting to %s:%i\n", ip.c_str(), port);
log("Connecting to %s:%i", ip.c_str(), port);
while(private_data->socket->getState() != Arcus::SocketState::Connected && private_data->socket->getState() != Arcus::SocketState::Error)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
log("Connected to %s:%i\n", ip.c_str(), port);
log("Connected to %s:%i", ip.c_str(), port);
bool slice_another_time = true;
@@ -152,49 +120,37 @@ void CommandSocket::connect(const std::string& ip, int port)
{
// Actually start handling messages.
Arcus::MessagePtr message = private_data->socket->takeNextMessage();
/*
* handle a message which consists purely of a SettingList
cura::proto::SettingList* setting_list = dynamic_cast<cura::proto::SettingList*>(message.get());
if (setting_list)
if(setting_list)
{
handleSettingList(setting_list);
}
*/
/*
* handle a message which consists purely of an ObjectList
cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
if (object_list)
/*cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
if(object_list)
{
handleObjectList(object_list);
}
*/
// Handle the main Slice message
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get()); // See if the message is of the message type Slice; returns nullptr otherwise
if (slice)
}*/
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get());
if(slice)
{
const cura::proto::SettingList& global_settings = slice->global_settings();
for (auto setting : global_settings.settings())
{
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
}
// Reset object counts
private_data->object_count = 0;
for (auto object : slice->object_lists())
private_data->object_ids.clear();
for(auto object : slice->object_lists())
{
handleObjectList(&object, slice->extruders());
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)
FffProcessor::getInstance()->resetFileNumber();
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!");
}
@@ -202,7 +158,7 @@ void CommandSocket::connect(const std::string& ip, int port)
private_data->objects_to_slice.clear();
FffProcessor::getInstance()->finalize();
flushGcode();
sendPrintTimeMaterialEstimates();
sendPrintTime();
sendFinishedSlicing();
slice_another_time = false; // TODO: remove this when multiple slicing with CuraEngine is safe
//TODO: Support all-at-once/one-at-a-time printing
@@ -210,20 +166,24 @@ void CommandSocket::connect(const std::string& ip, int port)
//private_data->object_to_slice.reset();
//private_data->processor->resetFileNumber();
//sendPrintTimeMaterialEstimates();
//sendPrintTime();
}
if(private_data->socket->getLastError().isValid())
{
logError("%s\n", private_data->socket->getLastError().toString().c_str());
private_data->socket->clearError();
}
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}
log("Closing connection\n");
private_data->socket->close();
#endif
}
#ifdef ARCUS
void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google::protobuf::RepeatedPtrField<cura::proto::Extruder> settings_per_extruder_train)
void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
{
if (list->objects_size() <= 0)
if(list->objects_size() <= 0)
{
return;
}
@@ -233,46 +193,30 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google
//private_data->object_ids.clear();
private_data->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
MeshGroup* meshgroup = private_data->objects_to_slice.back().get();
// load meshgroup settings
for (auto setting : list->settings())
for(auto setting : list->settings())
{
meshgroup->setSetting(setting.name(), setting.value());
}
{ // load extruder settings
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
{ // initialize remaining extruder trains and load the defaults
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, train);
}
for (auto extruder : settings_per_extruder_train)
{
int extruder_nr = extruder.id();
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
for (auto setting : extruder.settings().settings())
{
train->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
}
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");
// Check to which extruder train this object belongs
int extruder_train_nr = 0; // assume extruder 0 if setting wasn't supplied
for (auto setting : object.settings())
int extruder_train_nr = 0; // TODO: make primary extruder configurable!
for(auto setting : object.settings())
{
if (setting.name() == "extruder_nr")
{
@@ -285,7 +229,7 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google
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);
@@ -306,38 +250,50 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google
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();
}
private_data->object_count++;
meshgroup->finalize();
}
#endif
void CommandSocket::handleSettingList(cura::proto::SettingList* list)
{
for(auto setting : list->settings())
{
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
}
}
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
{
#ifdef ARCUS
std::shared_ptr<cura::proto::Layer> layer = private_data->getLayerById(layer_nr);
if(!private_data->current_sliced_object)
{
return;
}
cura::proto::Layer* layer = private_data->getLayerById(layer_nr);
layer->set_height(z);
layer->set_thickness(height);
#endif
}
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;
std::shared_ptr<cura::proto::Layer> proto_layer = private_data->getLayerById(layer_nr);
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));
@@ -346,18 +302,15 @@ void CommandSocket::sendPolygons(PrintFeatureType type, int layer_nr, Polygons&
p->set_points(polydata);
p->set_line_width(line_width);
}
#endif
}
void CommandSocket::sendProgress(float amount)
{
#ifdef ARCUS
auto message = std::make_shared<cura::proto::Progress>();
amount /= private_data->object_count;
amount += private_data->sliced_objects * (1. / private_data->object_count);
message->set_amount(amount);
private_data->socket->sendMessage(message);
#endif
}
void CommandSocket::sendProgressStage(Progress::Stage stage)
@@ -365,23 +318,12 @@ void CommandSocket::sendProgressStage(Progress::Stage stage)
// TODO
}
void CommandSocket::sendPrintTimeMaterialEstimates()
void CommandSocket::sendPrintTime()
{
#ifdef ARCUS
auto message = std::make_shared<cura::proto::PrintTimeMaterialEstimates>();
auto message = std::make_shared<cura::proto::ObjectPrintTime>();
message->set_time(FffProcessor::getInstance()->getTotalPrintTime());
int num_extruders = FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count");
for (int extruder_nr (0); extruder_nr < num_extruders; ++extruder_nr)
{
cura::proto::MaterialEstimates* material_message = message->add_materialestimates();
material_message->set_id(extruder_nr);
material_message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(extruder_nr));
}
message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(0));
private_data->socket->sendMessage(message);
#endif
}
void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float print_time)
@@ -393,88 +335,83 @@ void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float
// socket.sendFloat32(print_time);
}
void CommandSocket::sendLayerData()
void CommandSocket::beginSendSlicedObject()
{
if(!private_data->sliced_object_list)
{
private_data->sliced_object_list = std::make_shared<cura::proto::SlicedObjectList>();
}
private_data->current_sliced_object = private_data->sliced_object_list->add_objects();
private_data->current_sliced_object->set_id(private_data->object_ids[private_data->sliced_objects]);
}
void CommandSocket::endSendSlicedObject()
{
#ifdef ARCUS
#endif
#ifdef ARCUS
private_data->sliced_objects++;
private_data->current_layer_offset = private_data->current_layer_count;
log("End sliced object called. Sending ", private_data->current_layer_count, " layers.");
std::cout << "End sliced object called. Sliced objects " << private_data->sliced_objects << " object count: " << private_data->object_count << std::endl;
if (private_data->sliced_objects >= private_data->object_count)
if(private_data->sliced_objects >= private_data->object_count)
{
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->socket->sendMessage(private_data->sliced_object_list);
private_data->sliced_objects = 0;
private_data->current_layer_count = 0;
private_data->current_layer_offset = 0;
private_data->sliced_layers.clear();
private_data->sliced_object_list.reset();
private_data->current_sliced_object = nullptr;
auto done_message = std::make_shared<cura::proto::SlicingFinished>();
private_data->socket->sendMessage(done_message);
}
#endif
}
void CommandSocket::sendFinishedSlicing()
{
#ifdef ARCUS
std::shared_ptr<cura::proto::SlicingFinished> done_message = std::make_shared<cura::proto::SlicingFinished>();
private_data->socket->sendMessage(done_message);
#endif
}
void CommandSocket::beginGCode()
{
#ifdef ARCUS
FffProcessor::getInstance()->setTargetStream(&private_data->gcode_output_stream);
#endif
}
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);
private_data->gcode_output_stream.str("");
#endif
}
void CommandSocket::sendGCodePrefix(std::string prefix)
{
#ifdef ARCUS
auto message = std::make_shared<cura::proto::GCodePrefix>();
message->set_data(prefix);
private_data->socket->sendMessage(message);
#endif
}
#ifdef ARCUS
std::shared_ptr<cura::proto::Layer> CommandSocket::Private::getLayerById(int id)
cura::proto::Layer* CommandSocket::Private::getLayerById(int id)
{
id += current_layer_offset;
auto itr = sliced_layers.find(id);
auto itr = std::find_if(current_sliced_object->mutable_layers()->begin(), current_sliced_object->mutable_layers()->end(), [id](cura::proto::Layer& l) { return l.id() == id; });
std::shared_ptr<cura::proto::Layer> layer;
if (itr != sliced_layers.end())
cura::proto::Layer* layer = nullptr;
if(itr != current_sliced_object->mutable_layers()->end())
{
layer = itr->second;
layer = &(*itr);
}
else
{
layer = std::make_shared<cura::proto::Layer>();
layer = current_sliced_object->add_layers();
layer->set_id(id);
current_layer_count++;
sliced_layers[id] = layer;
}
return layer;
}
#endif
}//namespace cura
+20 -23
Ver Arquivo
@@ -3,18 +3,16 @@
#include "utils/socket.h"
#include "utils/polygon.h"
#include "settings/settings.h"
#include "progress/Progress.h"
#include "settings.h"
#include "Progress.h"
#include "PrintFeature.h"
#include <memory>
#ifdef ARCUS
#include "Cura.pb.h"
#endif
namespace cura
{
namespace cura {
class CommandSocket
{
@@ -37,19 +35,18 @@ public:
* \param port int of the port to connect with.
*/
void connect(const std::string& ip, int port);
#ifdef ARCUS
/*!
* Handler for ObjectList message.
* Loads all objects from the message and starts the slicing process
*
* Also handles meshgroup settings and extruder settings.
*
* \param[in] list The list of objects to slice
* \param[in] settings_per_extruder_train The extruder train settings to load into the meshgroup
*/
void handleObjectList(cura::proto::ObjectList* list, const google::protobuf::RepeatedPtrField<cura::proto::Extruder> settings_per_extruder_train);
#endif
void handleObjectList(cura::proto::ObjectList* list);
/*!
* Handler for SettingList message.
* This simply sets all the settings by using key value pair
*/
void handleSettingList(cura::proto::SettingList* list);
/*!
* Send info on a layer to be displayed by the forntend: set the z and the thickness of the layer.
@@ -79,20 +76,22 @@ public:
/*!
* Send time estimate of how long print would take.
*/
void sendPrintTimeMaterialEstimates();
void sendPrintTime();
/*!
* Does nothing at the moment
*/
void sendPrintMaterialForObject(int index, int extruder_nr, float material_amount);
/*!
* Start the slicing of a new meshgroup
*/
void beginSendSlicedObject();
/*!
* 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.
* Conclude the slicing of the current meshgroup, so that we can start the next
*/
void sendLayerData();
void endSendSlicedObject();
/*!
* \brief Sends a message to indicate that all the slicing is done.
@@ -110,11 +109,9 @@ public:
void flushGcode();
void sendGCodePrefix(std::string prefix);
#ifdef ARCUS
private:
class Private;
const std::unique_ptr<Private> private_data;
#endif
};
}//namespace cura
+143 -365
Ver Arquivo
@@ -6,7 +6,6 @@
#include "gcodeExport.h"
#include "utils/logoutput.h"
#include "PrintFeature.h"
#include "utils/Date.h"
namespace cura {
@@ -15,167 +14,21 @@ GCodeExport::GCodeExport()
, currentPosition(0,0,MM2INT(20))
, layer_nr(0)
{
*output_stream << std::fixed;
current_e_value = 0;
current_extruder = 0;
currentFanSpeed = -1;
totalPrintTime = 0.0;
currentSpeed = 1;
current_acceleration = -1;
current_jerk = -1;
isZHopped = 0;
setFlavor(EGCodeFlavor::REPRAP);
initial_bed_temp = 0;
extruder_count = 0;
}
GCodeExport::~GCodeExport()
{
}
void GCodeExport::preSetup(const 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 (const Mesh& mesh : settings->meshes)
{
extruder_attr[mesh.getSettingAsIndex("extruder_nr")].is_used = true;
}
for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
{
const ExtruderTrain* train = settings->getExtruderTrain(extruder_nr);
if (settings->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr)
|| (settings->getSettingBoolean("support_enable") && settings->getSettingAsIndex("support_infill_extruder_nr") == int(extruder_nr))
|| (settings->getSettingBoolean("support_enable") && settings->getSettingAsIndex("support_extruder_nr_layer_0") == int(extruder_nr))
|| (settings->getSettingBoolean("support_enable") && settings->getSettingBoolean("support_roof_enable") && settings->getSettingAsIndex("support_roof_extruder_nr") == int(extruder_nr))
)
{
extruder_attr[extruder_nr].is_used = true;
}
setFilamentDiameter(extruder_nr, train->getSettingInMicrons("material_diameter"));
extruder_attr[extruder_nr].prime_pos = Point3(train->getSettingInMicrons("extruder_prime_pos_x"), train->getSettingInMicrons("extruder_prime_pos_y"), train->getSettingInMicrons("extruder_prime_pos_z"));
extruder_attr[extruder_nr].prime_pos_is_abs = train->getSettingBoolean("extruder_prime_pos_abs");
extruder_attr[extruder_nr].nozzle_size = train->getSettingInMicrons("machine_nozzle_size");
extruder_attr[extruder_nr].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
extruder_attr[extruder_nr].start_code = train->getSettingString("machine_extruder_start_code");
extruder_attr[extruder_nr].end_code = train->getSettingString("machine_extruder_end_code");
extruder_attr[extruder_nr].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++)
{
if (!extruder_attr[extr_nr].is_used)
{
continue;
}
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_;
}
@@ -186,16 +39,6 @@ void GCodeExport::setOutputStream(std::ostream* stream)
*output_stream << std::fixed;
}
bool GCodeExport::getExtruderIsUsed(int extruder_nr)
{
return extruder_attr[extruder_nr].is_used;
}
int GCodeExport::getNozzleSize(int extruder_nr)
{
return extruder_attr[extruder_nr].nozzle_size;
}
Point GCodeExport::getExtruderOffset(int id)
{
return extruder_attr[id].nozzle_offset;
@@ -290,48 +133,12 @@ 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)
double GCodeExport::getTotalFilamentUsed(int e)
{
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)
{
if (extruder_nr == current_extruder)
return extruder_attr[extruder_nr].totalFilament + getCurrentExtrudedVolume();
return extruder_attr[extruder_nr].totalFilament;
if (e == current_extruder)
return extruder_attr[e].totalFilament + getCurrentExtrudedVolume();
return extruder_attr[e].totalFilament;
}
double GCodeExport::getTotalPrintTime()
@@ -355,7 +162,6 @@ void GCodeExport::updateTotalPrintTime()
{
totalPrintTime += estimateCalculator.calculate();
estimateCalculator.reset();
writeTimeComment(totalPrintTime);
}
void GCodeExport::writeComment(std::string comment)
@@ -370,12 +176,12 @@ void GCodeExport::writeComment(std::string comment)
*output_stream << comment[i];
}
}
*output_stream << new_line;
*output_stream << "\n";
}
void GCodeExport::writeTimeComment(const double time)
void GCodeExport::writeTypeComment(const char* type)
{
*output_stream << ";TIME_ELAPSED:" << time << new_line;
*output_stream << ";TYPE:" << type << "\n";
}
void GCodeExport::writeTypeComment(PrintFeatureType type)
@@ -383,25 +189,25 @@ void GCodeExport::writeTypeComment(PrintFeatureType type)
switch (type)
{
case PrintFeatureType::OuterWall:
*output_stream << ";TYPE:WALL-OUTER" << new_line;
*output_stream << ";TYPE:WALL-OUTER\n";
break;
case PrintFeatureType::InnerWall:
*output_stream << ";TYPE:WALL-INNER" << new_line;
*output_stream << ";TYPE:WALL-INNER\n";
break;
case PrintFeatureType::Skin:
*output_stream << ";TYPE:SKIN" << new_line;
*output_stream << ";TYPE:SKIN\n";
break;
case PrintFeatureType::Support:
*output_stream << ";TYPE:SUPPORT" << new_line;
*output_stream << ";TYPE:SUPPORT\n";
break;
case PrintFeatureType::Skirt:
*output_stream << ";TYPE:SKIRT" << new_line;
*output_stream << ";TYPE:SKIRT\n";
break;
case PrintFeatureType::Infill:
*output_stream << ";TYPE:FILL" << new_line;
*output_stream << ";TYPE:FILL\n";
break;
case PrintFeatureType::SupportInfill:
*output_stream << ";TYPE:SUPPORT" << new_line;
*output_stream << ";TYPE:SUPPORT\n";
break;
case PrintFeatureType::MoveCombing:
case PrintFeatureType::MoveRetraction:
@@ -414,24 +220,24 @@ void GCodeExport::writeTypeComment(PrintFeatureType type)
void GCodeExport::writeLayerComment(int layer_nr)
{
*output_stream << ";LAYER:" << layer_nr << new_line;
*output_stream << ";LAYER:" << layer_nr << "\n";
}
void GCodeExport::writeLayerCountComment(int layer_count)
{
*output_stream << ";LAYER_COUNT:" << layer_count << new_line;
*output_stream << ";LAYER_COUNT:" << layer_count << "\n";
}
void GCodeExport::writeLine(const char* line)
{
*output_stream << line << new_line;
*output_stream << line << "\n";
}
void GCodeExport::resetExtrusionValue()
{
if (flavor != EGCodeFlavor::MAKERBOT && flavor != EGCodeFlavor::BFB)
if (current_e_value != 0.0 && flavor != EGCodeFlavor::MAKERBOT && flavor != EGCodeFlavor::BFB)
{
*output_stream << "G92 " << extruder_attr[current_extruder].extruderCharacter << "0" << new_line;
*output_stream << "G92 " << extruder_attr[current_extruder].extruderCharacter << "0\n";
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)
@@ -445,7 +251,7 @@ void GCodeExport::resetExtrusionValue()
void GCodeExport::writeDelay(double timeAmount)
{
*output_stream << "G4 P" << int(timeAmount * 1000) << new_line;
*output_stream << "G4 P" << int(timeAmount * 1000) << "\n";
estimateCalculator.addTime(timeAmount);
}
@@ -461,7 +267,11 @@ 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 = mm3ToE(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;
}
Point gcode_pos = getGcodePos(x,y, current_extruder);
@@ -478,11 +288,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 << new_line;
*output_stream << "M108 S" << std::setprecision(1) << rpm << "\r\n";
currentSpeed = double(rpm);
}
//Add M101 or M201 to enable the proper extruder.
*output_stream << "M" << int((current_extruder + 1) * 100 + 1) << new_line;
*output_stream << "M" << int((current_extruder + 1) * 100 + 1) << "\r\n";
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.
@@ -499,17 +309,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" << new_line;
*output_stream << "M103\r\n";
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 << new_line;
" Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n";
currentPosition = Point3(x, y, z);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), speed);
}
void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_mm3_per_mm)
@@ -532,7 +342,11 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
return;
}
double extrusion_per_mm = mm3ToE(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;
}
Point gcode_pos = getGcodePos(x,y, current_extruder);
@@ -541,30 +355,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) << new_line;
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << "\n";
isZHopped = 0;
}
double prime_volume = extruder_attr[current_extruder].prime_volume;
current_e_value += mm3ToE(prime_volume);
current_e_value += (is_volumatric) ? prime_volume : prime_volume / extruder_attr[current_extruder].filament_area;
if (extruder_attr[current_extruder].retraction_e_amount_current)
{
if (firmware_retract)
{ // note that BFB is handled differently
*output_stream << "G11" << new_line;
*output_stream << "G11\n";
//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 << new_line;
*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";
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
}
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), 25.0);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), 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 << new_line;
*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";
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), 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.
{
@@ -574,9 +388,10 @@ 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 << new_line;
current_e_value += prime_volume;
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
}
extruder_attr[current_extruder].prime_volume = 0.0;
current_e_value += extrusion_per_mm * diff.vSizeMM();
@@ -610,40 +425,31 @@ 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 << new_line;
*output_stream << "\n";
currentPosition = Point3(x, y, z);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), speed);
}
void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool extruder_switch)
void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
{
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;
}
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)
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)
{
return;
}
{ // handle retraction limitation
double current_extruded_volume = getCurrentExtrudedVolume();
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())
std::deque<double>& extruded_volume_at_previous_n_retractions = extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions;
while (int(extruded_volume_at_previous_n_retractions.size()) >= config->retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
{
// extruder switch could have introduced data which falls outside the retraction window
// also the retraction_count_max could have changed between the last retraction and this
@@ -653,132 +459,127 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool ext
{
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 * extr_attr.filament_area)
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max - 1
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config->retraction_extrusion_window * extruder_attr[current_extruder].filament_area)
{
return;
}
extruded_volume_at_previous_n_retractions.push_front(current_extruded_volume);
if (int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max + 1)
if (int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max)
{
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)
{
if (extruder_switch && extr_attr.retraction_e_amount_current)
{
return;
}
*output_stream << "G10";
if (extruder_switch)
{
*output_stream << " S1";
}
*output_stream << new_line;
*output_stream << "G10\n";
//Assume default UM2 retraction settings.
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!
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value - retraction_e_amount), 25); // TODO: hardcoded values!
}
else
{
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;
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);
}
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;
}
void GCodeExport::writeZhopStart(int hop_height)
{
if (hop_height > 0)
extruder_attr[current_extruder].retraction_e_amount_current = retraction_e_amount ;
extruder_attr[current_extruder].prime_volume += config->prime_volume;
if (config->zHop > 0)
{
isZHopped = hop_height;
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << new_line;
isZHopped = config->zHop;
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << "\n";
}
}
void GCodeExport::startExtruder(int new_extruder)
void GCodeExport::writeRetraction_extruderSwitch()
{
if (new_extruder != current_extruder) // wouldn't be the case on the very first extruder start if it's extruder 0
if (flavor == EGCodeFlavor::BFB)
{
if (flavor == EGCodeFlavor::MAKERBOT)
{
*output_stream << "M135 T" << new_extruder << new_line;
}
else
{
*output_stream << "T" << new_extruder << new_line;
}
if (!extruder_attr[current_extruder].retraction_e_amount_current)
*output_stream << "M103\r\n";
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;
}
current_extruder = new_extruder;
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;
}
assert(getCurrentExtrudedVolume() == 0.0 && "Just after an extruder switch we haven't extruded anything yet!");
resetExtrusionValue(); // zero the E value on the new extruder, just to be sure
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);
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;
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
}
void GCodeExport::switchExtruder(int new_extruder, const RetractionConfig& retraction_config_old_extruder)
void GCodeExport::switchExtruder(int new_extruder)
{
if (current_extruder == new_extruder)
return;
bool force = true;
bool extruder_switch = true;
writeRetraction(&const_cast<RetractionConfig&>(retraction_config_old_extruder), force, extruder_switch);
writeRetraction_extruderSwitch();
resetExtrusionValue(); // zero the E value on the old extruder, so that the current_e_value is registered on the old extruder
resetExtrusionValue(); // should be called 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());
startExtruder(new_extruder);
if (flavor == EGCodeFlavor::MAKERBOT)
{
*output_stream << "M135 T" << current_extruder << "\n";
}
else
{
*output_stream << "T" << current_extruder << "\n";
}
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 << new_line;
}
void GCodeExport::writePrimeTrain(double travel_speed)
{
if (extruder_attr[current_extruder].is_primed)
{ // extruder is already primed once!
return;
}
Point3 prime_pos = extruder_attr[current_extruder].prime_pos;
if (!extruder_attr[current_extruder].prime_pos_is_abs)
{
prime_pos += currentPosition;
}
writeMove(prime_pos, travel_speed, 0.0);
if (flavor == EGCodeFlavor::GRIFFIN)
{
*output_stream << "G280" << new_line;
}
*output_stream << str;
if (flavor == EGCodeFlavor::BFB)
*output_stream << "\r\n";
else
{
// there is no prime gcode for other firmware versions...
}
extruder_attr[current_extruder].is_primed = true;
*output_stream << "\n";
}
void GCodeExport::writeFanCommand(double speed)
{
if (currentFanSpeed == speed)
@@ -786,16 +587,16 @@ void GCodeExport::writeFanCommand(double speed)
if (speed > 0)
{
if (flavor == EGCodeFlavor::MAKERBOT)
*output_stream << "M126 T0" << new_line; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
*output_stream << "M126 T0\n"; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
else
*output_stream << "M106 S" << (speed * 255 / 100) << new_line;
*output_stream << "M106 S" << (speed * 255 / 100) << "\n";
}
else
{
if (flavor == EGCodeFlavor::MAKERBOT)
*output_stream << "M127 T0" << new_line;
*output_stream << "M127 T0\n";
else
*output_stream << "M107" << new_line;
*output_stream << "M107\n";
}
currentFanSpeed = speed;
}
@@ -811,7 +612,7 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
*output_stream << "M104";
if (extruder != current_extruder)
*output_stream << " T" << extruder;
*output_stream << " S" << temperature << new_line;
*output_stream << " S" << temperature << "\n";
extruder_attr[extruder].currentTemperature = temperature;
}
@@ -821,38 +622,15 @@ void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
*output_stream << "M190 S";
else
*output_stream << "M140 S";
*output_stream << temperature << new_line;
*output_stream << temperature << "\n";
}
void GCodeExport::writeAcceleration(double acceleration)
{
if (current_acceleration != acceleration)
{
*output_stream << "M204 S" << acceleration << new_line; // Print and Travel acceleration
current_acceleration = acceleration;
estimateCalculator.setAcceleration(acceleration);
}
}
void GCodeExport::writeJerk(double jerk)
{
if (current_jerk != jerk)
{
*output_stream << "M205 X" << jerk << new_line;
current_jerk = jerk;
estimateCalculator.setMaxXyJerk(jerk);
}
}
void GCodeExport::finalize(const char* endCode)
void GCodeExport::finalize(double moveSpeed, const char* endCode)
{
writeFanCommand(0);
writeCode(endCode);
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);
log("Filament: %d\n", mat_0);
log("Print time: %d\n", int(getTotalPrintTime()));
log("Filament: %d\n", int(getTotalFilamentUsed(0)));
for(int n=1; n<MAX_EXTRUDERS; n++)
if (getTotalFilamentUsed(n) > 0)
log("Filament%d: %d\n", n + 1, int(getTotalFilamentUsed(n)));
+154 -193
Ver Arquivo
@@ -6,30 +6,127 @@
#include <deque> // for extrusionAmountAtPreviousRetractions
#include <sstream> // for stream.str()
#include "settings/settings.h"
#include "settings.h"
#include "utils/intpoint.h"
#include "utils/NoCopy.h"
#include "timeEstimate.h"
#include "MeshGroup.h"
#include "commandSocket.h"
#include "RetractionConfig.h"
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; //!< 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
bool coasting_enable;
double coasting_volume;
double coasting_speed;
double coasting_min_volume;
};
class RetractionConfig
{
public:
double distance; //!< The distance retracted (in mm)
double speed; //!< The speed with which to retract (in mm/s)
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;
};
//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_iconic; //!< movement speed (mm/s) specific to this print feature
double speed; //!< current movement speed (mm/s) (modified by layer_nr etc.)
int line_width; //!< width of the line extruded
double flow; //!< extrusion flow in %
int layer_thickness; //!< layer height
double extrusion_mm3_per_mm;//!< mm^3 filament moved per mm line extruded
public:
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_iconic(0), speed(0), line_width(0), extrusion_mm3_per_mm(0.0), type(type), spiralize(false), retraction_config(retraction_config) {}
/*!
* Initialize some of the member variables.
*
* Warning! setLayerHeight still has to be called before this object can be used.
*/
void init(double speed, int line_width, double flow)
{
speed_iconic = speed;
this->speed = speed;
this->line_width = line_width;
this->flow = flow;
}
/*!
* Set the layer height and (re)compute the extrusion_per_mm
*/
void setLayerHeight(int layer_height)
{
this->layer_thickness = layer_height;
calculateExtrusion();
}
/*!
* Set the speed to somewhere between the @p min_speed and the speed_iconic.
*
* This functions should not be called with @p layer_nr > @p max_speed_layer !
*
* \param min_speed The speed at layer zero
* \param layer_nr The layer number
* \param max_speed_layer The layer number for which the speed_iconic should be used.
*/
void smoothSpeed(double min_speed, int layer_nr, double max_speed_layer)
{
speed = (speed_iconic*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
}
/*!
* Can only be called after the layer height has been set (which is done while writing the gcode!)
*/
double getExtrusionMM3perMM()
{
return extrusion_mm3_per_mm;
}
/*!
* Get the movement speed in mm/s
*/
double getSpeed()
{
return speed;
}
int getLineWidth()
{
return line_width;
}
bool isTravelPath()
{
return line_width == 0;
}
double getFlowPercentage()
{
return flow;
}
private:
void calculateExtrusion()
{
extrusion_mm3_per_mm = INT2MM(line_width) * INT2MM(layer_thickness) * double(flow) / 100.0;
}
};
//The GCodeExport class writes the actual GCode. This is the only class that knows how GCode looks and feels.
// Any customizations on GCodes flavors are done in this class.
@@ -38,21 +135,18 @@ class GCodeExport : public NoCopy
private:
struct ExtruderTrainAttributes
{
Point3 prime_pos; //!< The location this nozzle is primed before printing
bool prime_pos_is_abs; //!< Whether the prime position is absolute, rather than relative to the last given position
bool is_primed; //!< Whether this extruder has currently already been primed in this print
bool is_used; //!< Whether this extruder train is actually used during the printing of the current meshgroup
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
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.
@@ -63,39 +157,29 @@ private:
std::deque<double> extruded_volume_at_previous_n_retractions; // in mm^3
ExtruderTrainAttributes()
: prime_pos(0, 0, 0)
, prime_pos_is_abs(false)
, is_primed(false)
, is_used(false)
, nozzle_offset(0,0)
: nozzle_offset(0,0)
, extruderCharacter(0)
, 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)
, last_retraction_prime_speed(0.0)
, last_retraction_prime_speed(1.0)
{ }
};
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
double current_acceleration; //!< The current acceleration in the XY direction (in mm/s^2)
double current_jerk; //!< The current jerk in the XY direction (in mm/s^3)
int zPos; // TODO: why is this different from currentPosition.z ? zPos is set every layer, while currentPosition.z is set every move. However, the z position is generally not changed within a layer!
int isZHopped; //!< The amount by which the print head is currently z hopped, or zero if it is not z hopped. (A z hop is used during travel moves to avoid collision with other layer parts)
@@ -103,72 +187,23 @@ private:
int currentFanSpeed;
EGCodeFlavor flavor;
double totalPrintTime; //!< The total estimated print time in seconds
double totalPrintTime;
TimeEstimateCalculator estimateCalculator;
bool is_volumatric;
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);
bool getExtruderIsUsed(int extruder_nr); //!< Returns whether the extruder with the given index is used up until the current meshgroup
int getNozzleSize(int extruder_nr);
Point getExtruderOffset(int id);
Point getGcodePos(int64_t x, int64_t y, int extruder_train);
@@ -194,35 +229,16 @@ public:
void setFilamentDiameter(unsigned int n, int diameter);
double getCurrentExtrudedVolume();
double getTotalFilamentUsed(int e);
/*!
* Get the total extruded volume for a specific extruder in mm^3
*
* Retractions and unretractions don't contribute to this.
*
* \param extruder_nr The extruder number for which to get the total netto extruded volume
* \return total filament printed in mm^3
*/
double getTotalFilamentUsed(int extruder_nr);
/*!
* Get the total estimated print time in seconds
*
* \return total print time in seconds
*/
double getTotalPrintTime();
void updateTotalPrintTime();
void resetTotalPrintTimeAndFilament();
void writeComment(std::string comment);
void writeTypeComment(const char* type);
void writeTypeComment(PrintFeatureType type);
/*!
* Write a comment saying what (estimated) time has passed up to this point
*
* \param time The time passed up till this point
*/
void writeTimeComment(const double time);
void writeLayerComment(int layer_nr);
void writeLayerCountComment(int layer_count);
@@ -247,98 +263,43 @@ private:
*/
void writeMoveBFB(int x, int y, int z, double speed, double extrusion_per_mm);
public:
void writeRetraction(RetractionConfig* config, bool force = false, bool extruder_switch = false);
/*!
* Start a z hop with the given \p hop_height
*
* \param hop_height The height to move above the current layer
*/
void writeZhopStart(int hop_height);
/*!
* Start the new_extruder:
* - set new extruder
* - zero E value
* - write extruder start gcode
*
* \param new_extruder The extruder to start with
*/
void startExtruder(int new_extruder);
/*!
* Switch to the new_extruder:
* - perform neccesary retractions
* - fiddle with E-values
* - write extruder end gcode
* - set new extruder
* - write extruder start gcode
*
* \param new_extruder The extruder to switch to
* \param retraction_config_old_extruder The extruder switch retraction config of the old extruder, to perform the extruder switch retraction with.
*/
void switchExtruder(int new_extruder, const RetractionConfig& retraction_config_old_extruder);
void writeCode(const char* str);
void writeRetraction(RetractionConfig* config, bool force=false);
/*!
* Write the gcode for priming the current extruder train so that it can be used.
*
* \param travel_speed The travel speed when priming involves a movement
*/
void writePrimeTrain(double travel_speed);
void writeRetraction_extruderSwitch();
void switchExtruder(int newExtruder);
void writeCode(const char* str);
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");
}
/*!
* Write the command for setting the acceleration to a specific value
*/
void writeAcceleration(double acceleration);
/*!
* Write the command for setting the jerk to a specific value
*/
void writeJerk(double jerk);
/*!
* 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(const 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
+168 -279
Ver Arquivo
@@ -22,20 +22,22 @@ TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstim
return *this;
}
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow)
{
std::vector<GCodePath>& paths = extruder_plans.back().paths;
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
if (paths.size() > 0 && paths.back().config == config && !paths.back().done && paths.back().flow == flow)
return &paths.back();
paths.emplace_back();
GCodePath* ret = &paths.back();
ret->retract = false;
ret->perform_z_hop = false;
ret->config = config;
ret->done = false;
ret->flow = flow;
ret->spiralize = spiralize;
ret->space_fill_type = space_fill_type;
if (config != &storage.travel_config)
{
last_retraction_config = config->retraction_config;
}
return ret;
}
@@ -46,28 +48,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, 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::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
: storage(storage)
, layer_nr(layer_nr)
, z(z)
, layer_thickness(layer_thickness)
, start_position(last_position)
, lastPosition(last_position)
, last_extruder_previous_layer(current_extruder)
, last_planned_extruder_setting_base(storage.meshgroup->getExtruderTrain(current_extruder))
, comb_boundary_inside(computeCombBoundaryInside(combing_mode))
, comb_boundary_inside(computeCombBoundaryInside())
, 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 = is_inside_mesh;
is_inside = false; // assumes the next move will not be to inside a layer part (overwritten just before going into a layer part)
was_inside = 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
setExtrudeSpeedFactor(1.0);
setTravelSpeedFactor(1.0);
extraTime = 0.0;
totalPrintTime = 0.0;
if (combing_mode != CombingMode::OFF)
if (retraction_combing)
{
comb = new Comb(storage, layer_nr, comb_boundary_inside, comb_boundary_offset, travel_avoid_other_parts, travel_avoid_distance);
}
@@ -81,28 +82,11 @@ GCodePlanner::~GCodePlanner()
delete comb;
}
SettingsBaseVirtual* GCodePlanner::getLastPlannedExtruderTrainSettings()
Polygons GCodePlanner::computeCombBoundaryInside()
{
return last_planned_extruder_setting_base;
}
Polygons GCodePlanner::computeCombBoundaryInside(CombingMode combing_mode)
{
if (combing_mode == CombingMode::OFF)
{
return Polygons();
}
if (layer_nr < 0)
{ // when a raft is present
if (combing_mode == CombingMode::NO_SKIN)
{
return Polygons();
}
else
{
return storage.raftOutline.offset(MM2INT(0.1));
}
return storage.raftOutline.offset(MM2INT(0.1));
}
else
{
@@ -110,21 +94,7 @@ Polygons GCodePlanner::computeCombBoundaryInside(CombingMode combing_mode)
for (SliceMeshStorage& mesh : storage.meshes)
{
SliceLayer& layer = mesh.layers[layer_nr];
if (mesh.getSettingAsCombingMode("retraction_combing") == CombingMode::NO_SKIN)
{
for (SliceLayerPart& part : layer.parts)
{
layer_walls.add(part.infill_area);
}
}
else
{
if (mesh.getSettingBoolean("infill_mesh"))
{
continue;
}
layer.getSecondOrInnermostWalls(layer_walls);
}
layer.getSecondOrInnermostWalls(layer_walls);
}
return layer_walls;
}
@@ -137,14 +107,14 @@ void GCodePlanner::setIsInside(bool _is_inside)
bool GCodePlanner::setExtruder(int extruder)
{
if (extruder == getExtruder())
if (extruder == extruder_plans.back().extruder)
{
return false;
}
setIsInside(false);
{ // handle end position of the prev extruder
SettingsBaseVirtual* train = getLastPlannedExtruderTrainSettings();
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder_plans.back().extruder);
bool end_pos_absolute = train->getSettingBoolean("machine_extruder_end_pos_abs");
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
Point end_pos(train->getSettingInMicrons("machine_extruder_end_pos_x"), train->getSettingInMicrons("machine_extruder_end_pos_y"));
if (!end_pos_absolute)
{
@@ -152,26 +122,18 @@ bool GCodePlanner::setExtruder(int extruder)
}
else
{
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
end_pos += extruder_offset; // absolute end pos is given as a head position
}
addTravel(end_pos); // + extruder_offset cause it
}
if (extruder_plans.back().paths.empty() && extruder_plans.back().inserts.empty())
{ // first extruder plan in a layer might be empty, cause it is made with the last extruder planned in the previous layer
extruder_plans.back().extruder = extruder;
}
else
{
extruder_plans.emplace_back(extruder);
}
last_planned_extruder_setting_base = storage.meshgroup->getExtruderTrain(extruder);
extruder_plans.emplace_back(extruder);
// forceNewPathStart(); // automatic by the fact that we start a new ExtruderPlan
{ // handle starting pos of the new extruder
SettingsBaseVirtual* train = getLastPlannedExtruderTrainSettings();
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder);
bool start_pos_absolute = train->getSettingBoolean("machine_extruder_start_pos_abs");
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
Point start_pos(train->getSettingInMicrons("machine_extruder_start_pos_x"), train->getSettingInMicrons("machine_extruder_start_pos_y"));
if (!start_pos_absolute)
{
@@ -179,7 +141,6 @@ bool GCodePlanner::setExtruder(int extruder)
}
else
{
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
start_pos += extruder_offset; // absolute start pos is given as a head position
}
lastPosition = start_pos;
@@ -208,86 +169,76 @@ void GCodePlanner::moveInsideCombBoundary(int distance)
void GCodePlanner::addTravel(Point p)
{
GCodePath* path = nullptr;
GCodePathConfig& travel_config = storage.travel_config_per_extruder[getExtruder()];
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[getExtruder()];
bool combed = false;
SettingsBaseVirtual* extr = getLastPlannedExtruderTrainSettings();
const bool perform_z_hops = extr->getSettingBoolean("retraction_hop_enabled");
const bool is_first_travel_of_extruder_after_switch = extruder_plans.back().paths.size() == 0 && (extruder_plans.size() > 1 || last_extruder_previous_layer != getExtruder());
const bool bypass_combing = is_first_travel_of_extruder_after_switch && extr->getSettingBoolean("retraction_hop_after_extruder_switch");
if (comb != nullptr && !bypass_combing && lastPosition != no_point)
if (comb != nullptr && lastPosition != no_point)
{
const bool perform_z_hops_only_when_collides = extr->getSettingBoolean("retraction_hop_only_when_collides");
CombPaths combPaths;
bool via_outside_makes_combing_fail = perform_z_hops && !perform_z_hops_only_when_collides;
bool fail_on_unavoidable_obstacles = perform_z_hops && perform_z_hops_only_when_collides;
combed = comb->calc(lastPosition, p, combPaths, was_inside, is_inside, retraction_config.retraction_min_travel_distance, via_outside_makes_combing_fail, fail_on_unavoidable_obstacles);
combed = comb->calc(lastPosition, p, combPaths, was_inside, is_inside, last_retraction_config->retraction_min_travel_distance);
if (combed)
{
bool retract = combPaths.size() > 1;
if (!retract)
{ // check whether we want to retract
if (combPaths.throughAir)
{
retract = true;
}
else
{
for (CombPath& combPath : combPaths)
{ // retract when path moves through a boundary
if (combPath.cross_boundary)
{
retract = true;
break;
}
for (CombPath& combPath : combPaths)
{ // retract when path moves through a boundary
if (combPath.cross_boundary || combPath.throughAir)
{
retract = true;
break;
}
}
if (combPaths.size() == 1)
{
CombPath path = combPaths[0];
if (combPaths.throughAir && !path.cross_boundary && path.size() == 2 && path[0] == lastPosition && path[1] == p)
if (path.throughAir && !path.cross_boundary && path.size() == 2 && path[0] == lastPosition && path[1] == p)
{ // limit the retractions from support to support, which didn't cross anything
retract = false;
}
}
}
for (CombPath& combPath : combPaths)
{ // add all comb paths (don't do anything special for paths which are moving through air)
if (combPath.size() == 0)
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);
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
{
continue;
path->retract = true;
}
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
path->retract = retract;
// don't perform a z-hop
for (Point& combPoint : combPath)
{
path->points.push_back(combPoint);
}
else
{
for (CombPath& combPath : combPaths)
{ // add all comb paths (don't do anything special for paths which are moving through air)
if (combPath.size() == 0)
{
continue;
}
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
path->retract = retract;
for (Point& combPoint : combPath)
{
path->points.push_back(combPoint);
}
lastPosition = combPath.back();
}
lastPosition = combPath.back();
}
}
}
if (!combed) {
// no combing? always retract!
if (!shorterThen(lastPosition - p, retraction_config.retraction_min_travel_distance))
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
{
if (was_inside) // when the previous location was from printing something which is considered inside (not support or prime tower etc)
{ // then move inside the printed part, so that we don't ooze on the outer wall while retraction, but on the inside of the print.
ExtruderTrain* extr = storage.meshgroup->getExtruderTrain(getExtruder());
assert (extr != nullptr);
moveInsideCombBoundary(extr->getSettingInMicrons((extr->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
moveInsideCombBoundary(extr->getSettingInMicrons("machine_nozzle_size") * 1);
}
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
path->retract = true;
path->perform_z_hop = perform_z_hops;
}
}
@@ -299,39 +250,33 @@ void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
{
if (path == nullptr)
{
path = getLatestPathWithConfig(&storage.travel_config_per_extruder[getExtruder()], SpaceFillType::None);
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
}
#ifdef ASSERT_INSANE_OUTPUT
assert(p != no_point && p != Point(0, 0) && "Uninitialised travel location!");
#endif //ASSERT_INSANE_OUTPUT
path->points.push_back(p);
lastPosition = p;
}
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow)
{
getLatestPathWithConfig(config, space_fill_type, flow, spiralize)->points.push_back(p);
getLatestPathWithConfig(config, space_fill_type, flow)->points.push_back(p);
lastPosition = p;
}
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, bool spiralize)
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation)
{
Point p0 = polygon[startIdx];
addTravel(p0);
for(unsigned int i=1; i<polygon.size(); i++)
{
Point p1 = polygon[(startIdx + i) % polygon.size()];
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
addExtrusionMove(p1, config, SpaceFillType::Polygons, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
p0 = p1;
}
if (polygon.size() > 2)
{
Point& p1 = polygon[startIdx];
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
addExtrusionMove(p1, config, SpaceFillType::Polygons, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
}
else
{
@@ -339,35 +284,30 @@ void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig*
}
}
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type, bool spiralize)
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type)
{
if (polygons.size() == 0)
{
return;
}
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_type);
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
orderOptimizer.addPolygon(polygons[poly_idx]);
}
for(unsigned int i=0;i<polygons.size();i++)
orderOptimizer.addPolygon(polygons[i]);
orderOptimizer.optimize();
for (unsigned int poly_idx : orderOptimizer.polyOrder)
for(unsigned int i=0;i<orderOptimizer.polyOrder.size();i++)
{
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation, spiralize);
int nr = orderOptimizer.polyOrder[i];
addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config, wall_overlap_computation);
}
}
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
{
LineOrderOptimizer orderOptimizer(lastPosition);
for (unsigned int line_idx = 0; line_idx < polygons.size(); line_idx++)
{
orderOptimizer.addPolygon(polygons[line_idx]);
}
LineOrderOptimizer orderOptimizer(lastPosition, storage.getSettingInMicrons("infill_line_distance")); //Use infill line distance to make adjacent infill lines fall in the same cluster.
for(unsigned int i=0;i<polygons.size();i++)
orderOptimizer.addPolygon(polygons[i]);
orderOptimizer.optimize();
for (int poly_idx : orderOptimizer.polyOrder)
for(unsigned int i=0;i<orderOptimizer.polyOrder.size();i++)
{
PolygonRef polygon = polygons[poly_idx];
int start = orderOptimizer.polyStart[poly_idx];
int nr = orderOptimizer.polyOrder[i];
// addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config);
PolygonRef polygon = polygons[nr];
int start = orderOptimizer.polyStart[nr];
int end = 1 - start;
Point& p0 = polygon[start];
addTravel(p0);
@@ -469,7 +409,7 @@ TimeMaterialEstimates GCodePlanner::computeNaiveTimeEstimates()
if (path.retract != was_retracted)
{ // handle retraction times
double retract_unretract_time;
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[extr_plan.extruder];
RetractionConfig& retraction_config = *path.config->retraction_config;
if (path.retract)
{
retract_unretract_time = retraction_config.distance / retraction_config.speed;
@@ -505,21 +445,7 @@ void GCodePlanner::processFanSpeedAndMinimalLayerTime()
FanSpeedLayerTimeSettings& fsml = fan_speed_layer_time_settings;
TimeMaterialEstimates estimates = computeNaiveTimeEstimates();
forceMinimalLayerTime(fsml.cool_min_layer_time, fsml.cool_min_speed, estimates.getTravelTime(), estimates.getExtrudeTime());
/*
min layer time
:
: min layer time fan speed min
| : :
^ max..|__: :
| \ :
fan | \ :
speed min..|... \:___________
|________________
layer time >
*/
// interpolate fan speed (for cool_fan_full_layer and for cool_min_layer_time_fan_speed_max)
fan_speed = fsml.cool_fan_speed_min;
double totalLayerTime = estimates.unretracted_travel_time + estimates.extrude_time;
@@ -530,25 +456,8 @@ void GCodePlanner::processFanSpeedAndMinimalLayerTime()
else if (totalLayerTime < fsml.cool_min_layer_time_fan_speed_max)
{
// when forceMinimalLayerTime didn't change the extrusionSpeedFactor, we adjust the fan speed
double fan_speed_diff = fsml.cool_fan_speed_max - fsml.cool_fan_speed_min;
double layer_time_diff = fsml.cool_min_layer_time_fan_speed_max - fsml.cool_min_layer_time;
double fraction_of_slope = (totalLayerTime - fsml.cool_min_layer_time) / layer_time_diff;
fan_speed = fsml.cool_fan_speed_max - fan_speed_diff * fraction_of_slope;
fan_speed = fsml.cool_fan_speed_max - (fsml.cool_fan_speed_max-fsml.cool_fan_speed_min) * (totalLayerTime - fsml.cool_min_layer_time) / (fsml.cool_min_layer_time_fan_speed_max - fsml.cool_min_layer_time);
}
/*
Supposing no influence of minimal layer time; i.e. layer time > min layer time fan speed min:
max.. fan 'full' on layer
| :
| :
^ min..|..:________________
fan | /
speed | /
zero..|/__________________
layer nr >
*/
if (layer_nr < fsml.cool_fan_full_layer)
{
//Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0.
@@ -557,7 +466,7 @@ void GCodePlanner::processFanSpeedAndMinimalLayerTime()
}
void GCodePlanner::writeGCode(GCodeExport& gcode)
void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int layerThickness)
{
completeConfigs();
@@ -569,8 +478,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
gcode.writeFanCommand(fan_speed);
GCodePathConfig* last_extrusion_config = nullptr; // used to check whether we need to insert a TYPE comment in the gcode.
GCodePathConfig* last_extrusion_config = nullptr;
int extruder = gcode.getExtruderNr();
for(unsigned int extruder_plan_idx = 0; extruder_plan_idx < extruder_plans.size(); extruder_plan_idx++)
@@ -578,28 +486,11 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
ExtruderPlan& extruder_plan = extruder_plans[extruder_plan_idx];
if (extruder != extruder_plan.extruder)
{
int prev_extruder = extruder;
extruder = extruder_plan.extruder;
gcode.switchExtruder(extruder, storage.extruder_switch_retraction_config_per_extruder[prev_extruder]);
{ // require printing temperature to be met
constexpr bool wait = true;
gcode.writeTemperatureCommand(extruder, extruder_plan.required_temp, wait);
}
// prime extruder if it hadn't been used yet
gcode.writePrimeTrain(storage.meshgroup->getExtruderTrain(extruder)->getSettingInMillimetersPerSecond("speed_travel"));
if (extruder_plan.prev_extruder_standby_temp)
{ // turn off previous extruder
constexpr bool wait = false;
gcode.writeTemperatureCommand(prev_extruder, *extruder_plan.prev_extruder_standby_temp, wait);
}
gcode.switchExtruder(extruder);
}
std::vector<GCodePath>& paths = extruder_plan.paths;
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[gcode.getExtruderNr()];
extruder_plan.inserts.sort([](const NozzleTempInsert& a, const NozzleTempInsert& b) -> bool {
return a.path_idx < b.path_idx;
} );
@@ -609,25 +500,11 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
extruder_plan.handleInserts(path_idx, gcode);
GCodePath& path = paths[path_idx];
if (storage.getSettingBoolean("acceleration_enabled"))
{
gcode.writeAcceleration(path.config->getAcceleration());
}
if (storage.getSettingBoolean("jerk_enabled"))
{
gcode.writeJerk(path.config->getJerk());
}
if (path.retract)
{
gcode.writeRetraction(&retraction_config);
if (path.perform_z_hop)
{
gcode.writeZhopStart(retraction_config.zHop);
}
writeRetraction(gcode, extruder_plan_idx, path_idx);
}
if (!path.config->isTravelPath() && last_extrusion_config != path.config)
if (path.config != &storage.travel_config && last_extrusion_config != path.config)
{
gcode.writeTypeComment(path.config->type);
last_extrusion_config = path.config;
@@ -641,34 +518,38 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
int64_t nozzle_size = 400; // TODO
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 !!
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config, nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
{ // !! has effect on path_idx !!
// works when path_idx is the index of the travel move BEFORE the infill lines to be merged
continue;
}
if (path.config->isTravelPath())
if (path.config == &storage.travel_config)
{ // 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.spiralize;
if (!spiralize) // normal (extrusion) move (with coasting
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;
}
}
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, layer_thickness, coasting_config.coasting_volume, coasting_config.coasting_speed, coasting_config.coasting_min_volume);
coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layerThickness, coasting_config.coasting_volume, coasting_config.coasting_speed, coasting_config.coasting_min_volume);
}
if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above...
{ // normal path to gcode algorithm
@@ -676,8 +557,8 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
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->isTravelPath() // next move is extrusion
&& paths[path_idx+2].config->isTravelPath() // next next move is travel
&& 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
&& 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
@@ -701,34 +582,26 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
{ // 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 _path_idx = path_idx; _path_idx < paths.size() && !paths[_path_idx].isTravelPath(); _path_idx++)
for(unsigned int i=0; i<path.points.size(); i++)
{
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;
}
Point p1 = path.points[i];
totalLength += vSizeMM(p0 - p1);
p0 = p1;
}
float length = 0.0;
p0 = gcode.getPositionXY();
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());
}
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());
}
path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path
}
}
@@ -736,14 +609,17 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
}
gcode.updateTotalPrintTime();
if (storage.getSettingBoolean("cool_lift_head") && extraTime > 0.0)
if (liftHeadIfNeeded && extraTime > 0.0)
{
gcode.writeComment("Small layer, adding delay");
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[gcode.getExtruderNr()];
gcode.writeRetraction(&retraction_config);
if (last_extrusion_config)
{
bool extruder_switch_retract = false;// TODO: check whether we should do a retractoin_extruderSwitch; is the next path with a different extruder?
writeRetraction(gcode, extruder_switch_retract, last_extrusion_config->retraction_config);
}
gcode.setZ(gcode.getPositionZ() + MM2INT(3.0));
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.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.writeDelay(extraTime);
}
}
@@ -773,50 +649,63 @@ void GCodePlanner::completeConfigs()
void GCodePlanner::processInitialLayersSpeedup()
{
int initial_speedup_layers = storage.getSettingAsCount("speed_slowdown_layers");
double initial_speedup_layers = storage.getSettingAsCount("speed_slowdown_layers");
if (static_cast<int>(layer_nr) < initial_speedup_layers)
{
GCodePathConfig::BasicConfig initial_layer_speed_config;
int extruder_nr_support_infill = storage.getSettingAsIndex((layer_nr == 0)? "support_extruder_nr_layer_0" : "support_infill_extruder_nr");
initial_layer_speed_config.speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("speed_layer_0");
initial_layer_speed_config.acceleration = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("acceleration_layer_0");
initial_layer_speed_config.jerk = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("jerk_layer_0");
storage.support_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
int extruder_nr_support_roof = storage.getSettingAsIndex("support_roof_extruder_nr");
initial_layer_speed_config.speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("speed_layer_0");
initial_layer_speed_config.acceleration = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("acceleration_layer_0");
initial_layer_speed_config.jerk = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("jerk_layer_0");
storage.support_roof_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
for (SliceMeshStorage& mesh : storage.meshes)
double initial_layer_speed = storage.getSettingInMillimetersPerSecond("speed_layer_0");
storage.support_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
storage.support_roof_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
for(SliceMeshStorage& mesh : storage.meshes)
{
initial_layer_speed_config.speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
initial_layer_speed_config.acceleration = mesh.getSettingInMillimetersPerSecond("acceleration_layer_0");
initial_layer_speed_config.jerk = mesh.getSettingInMillimetersPerSecond("jerk_layer_0");
mesh.inset0_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
mesh.insetX_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
mesh.skin_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++)
initial_layer_speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
mesh.inset0_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
mesh.insetX_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
mesh.skin_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
{
mesh.infill_config[idx].smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
mesh.infill_config[idx].smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
}
}
}
else if (static_cast<int>(layer_nr) == initial_speedup_layers)
}
void GCodePlanner::writeRetraction(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx_travel_after)
{
if (makeRetractSwitchRetract(gcode, extruder_plan_idx, path_idx_travel_after))
{
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++)
gcode.writeRetraction_extruderSwitch();
}
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)
{
mesh.infill_config[idx].setSpeedIconic();
extrusion_retraction_config = paths[extrusion_path_idx].config->retraction_config;
break;
}
}
writeRetraction(gcode, false, extrusion_retraction_config);
}
}
void GCodePlanner::writeRetraction(GCodeExport& gcode, bool extruder_switch_retract, RetractionConfig* retraction_config)
{
if (extruder_switch_retract)
{
gcode.writeRetraction_extruderSwitch();
}
else
{
if (retraction_config)
{
gcode.writeRetraction(retraction_config);
}
else
{
gcode.writeRetraction(storage.travel_config.retraction_config);
}
}
}
+56 -192
Ver Arquivo
@@ -4,16 +4,14 @@
#include <vector>
#include "gcodeExport.h"
#include "pathPlanning/Comb.h"
#include "comb.h"
#include "utils/polygon.h"
#include "utils/logoutput.h"
#include "wallOverlap.h"
#include "commandSocket.h"
#include "FanSpeedLayerTime.h"
#include "SpaceFillType.h"
#include "GCodePathConfig.h"
#include "utils/optional.h"
namespace cura
{
@@ -59,19 +57,12 @@ class TimeMaterialEstimates
{
friend class GCodePlanner;
private:
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)
double extrude_time; //!< in seconds
double unretracted_travel_time; //!< in seconds
double retracted_travel_time; //!< in seconds
double material; //!< 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)
@@ -79,10 +70,6 @@ public:
, material(material)
{
}
/*!
* Basic constructor initializing all estimates to zero.
*/
TimeMaterialEstimates()
: extrude_time(0.0)
, unretracted_travel_time(0.0)
@@ -90,7 +77,7 @@ public:
, material(0.0)
{
}
/*!
* Set all estimates to zero.
*/
@@ -101,24 +88,12 @@ 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;
@@ -127,7 +102,7 @@ public:
material += other.material;
return *this;
}
/*!
* \brief Subtracts the specified estimates from these estimates and returns
* the result.
@@ -138,7 +113,7 @@ public:
* \return These estimates with the specified estimates subtracted.
*/
TimeMaterialEstimates operator-(const TimeMaterialEstimates& other);
/*!
* \brief Subtracts the specified elements from these estimates.
*
@@ -149,72 +124,29 @@ 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:
@@ -222,30 +154,18 @@ public:
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
float flow; //!< A type-independent flow configuration (used for wall overlap compensation)
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
bool perform_z_hop; //!< Whether to perform a z_hop in this path, which is assumed to be a travel 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();
}
/*!
* 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
* Can only be called after the layer height has been set (which is done while writing the gcode!)
*/
double getExtrusionMM3perMM()
{
@@ -262,72 +182,46 @@ 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; //!< The paths planned for this extruder
std::list<NozzleTempInsert> inserts; //!< The nozzle temperature command inserts, to be inserted in between paths
std::vector<GCodePath> paths;
std::list<NozzleTempInsert> inserts;
int extruder; //!< The extruder used for this paths in the current plan.
double required_temp; //!< The required temperature at the start of this extruder plan.
std::optional<double> prev_extruder_standby_temp; //!< The temperature to which to set the previous extruder. Not used if the previous extruder plan was the same extruder.
TimeMaterialEstimates estimates; //!< Accumulated time and material estimates for all planned paths within this extruder plan.
/*!
* Simple contructor.
*
* \warning Doesn't set the required temperature yet.
*
* \param extruder The extruder number for which this object is a plan.
*/
double required_temp;
TimeMaterialEstimates estimates;
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
*
* \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.
* Insert the inserts into gcode which should be inserted before @p path_idx
*/
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)
{
@@ -342,25 +236,18 @@ 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;
friend class GCodePlannerTest;
private:
SliceDataStorage& storage; //!< The polygon data obtained from FffPolygonProcessor
SliceDataStorage& storage;
int layer_nr; //!< The layer number of this layer plan
int layer_nr;
int z;
@@ -370,14 +257,14 @@ private:
Point lastPosition;
std::vector<ExtruderPlan> extruder_plans; //!< should always contain at least one ExtruderPlan
int last_extruder_previous_layer; //!< The last id of the extruder with which was printed in the previous layer
SettingsBaseVirtual* last_planned_extruder_setting_base; //!< The setting base of the last planned extruder.
bool was_inside; //!< Whether the last planned (extrusion) move was inside a layer part
bool is_inside; //!< Whether the destination of the next planned travel move is inside a layer part
Polygons comb_boundary_inside; //!< The boundary within which to comb, or to move into when performing a retraction.
Comb* comb;
RetractionConfig* last_retraction_config;
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings;
double extrudeSpeedFactor;
@@ -396,10 +283,9 @@ 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, bool spiralize = false);
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
/*!
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
@@ -418,23 +304,16 @@ 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, 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(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();
/*!
* Get the settings base of the last extruder planned.
* \return the settings base of the last extruder planned.
*/
SettingsBaseVirtual* getLastPlannedExtruderTrainSettings();
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(CombingMode combing_mode);
Polygons computeCombBoundaryInside();
public:
int getLayerNr()
@@ -447,13 +326,6 @@ 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
*/
@@ -536,36 +408,12 @@ 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, bool spiralize = false);
void addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
/*!
* 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 addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr);
/*!
* 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);
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST);
/*!
* Add lines to the gcode with optimized order.
@@ -591,7 +439,7 @@ public:
*
* \param gcode The gcode to write the planned paths to
*/
void writeGCode(GCodeExport& gcode);
void writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int layerThickness);
/*!
* Complete all GcodePathConfig s by
@@ -632,6 +480,22 @@ public:
*/
bool writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume);
/*!
* Write a retraction: either an extruder switch retraction or a normal retraction based on the last extrusion paths retraction config.
* \param gcode The gcode to write the planned paths to
* \param extruder_plan_idx The index of the current extruder plan
* \param path_idx_travel_after Index in GCodePlanner::paths to the travel move before which to do the retraction
*/
void writeRetraction(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx_travel_after);
/*!
* Write a retraction: either an extruder switch retraction or a normal retraction based on the given retraction config.
* \param gcode The gcode to write the planned paths to
* \param extruder_switch_retract Whether to write an extruder switch retract
* \param retraction_config The config used.
*/
void writeRetraction(GCodeExport& gcode, bool extruder_switch_retract, RetractionConfig* retraction_config);
/*!
* Applying speed corrections for minimal layer times and determine the fanSpeed.
*/
+43 -11
Ver Arquivo
@@ -6,7 +6,7 @@
namespace cura {
void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between)
{
if (in_outline.size() == 0) return;
if (line_distance == 0) return;
@@ -24,9 +24,16 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
generateTriangleInfill(result_lines);
break;
case EFillMethod::CONCENTRIC:
outline_offsetted = in_outline.offset(outline_offset - infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
PolygonUtils::offsetSafe(in_outline, outline_offset - infill_line_width / 2, infill_line_width, outline_offsetted, false); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
outline = &outline_offsetted;
generateConcentricInfill(*outline, result_polygons, line_distance);
if (abs(infill_line_width - line_distance) < 10)
{
generateConcentricInfillDense(*outline, result_polygons, in_between, remove_overlapping_perimeters);
}
else
{
generateConcentricInfill(*outline, result_polygons, line_distance);
}
break;
case EFillMethod::ZIG_ZAG:
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
@@ -37,11 +44,33 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
}
}
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)
{
result.add(outline);
for (unsigned int polyNr = 0; polyNr < outline.size(); polyNr++)
{
PolygonRef r = outline[polyNr];
result.add(r);
}
outline = outline.offset(-inset_value);
}
}
@@ -49,15 +78,15 @@ void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int in
void Infill::generateGridInfill(Polygons& result)
{
generateLineInfill(result, line_distance, fill_angle);
generateLineInfill(result, line_distance, fill_angle + 90);
generateLineInfill(result, line_distance * 2, fill_angle);
generateLineInfill(result, line_distance * 2, fill_angle + 90);
}
void Infill::generateTriangleInfill(Polygons& result)
{
generateLineInfill(result, line_distance, fill_angle);
generateLineInfill(result, line_distance, fill_angle + 60);
generateLineInfill(result, line_distance, fill_angle + 120);
generateLineInfill(result, line_distance * 3, fill_angle);
generateLineInfill(result, line_distance * 3, fill_angle + 60);
generateLineInfill(result, line_distance * 3, fill_angle + 120);
}
void 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)
@@ -172,14 +201,17 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
Polygons outline;
if (outline_offset != 0)
{
outline = in_outline.offset(outline_offset);
PolygonUtils::offsetSafe(in_outline, outline_offset, infill_line_width, outline, remove_overlapping_perimeters && safe_outline_offset);
}
else
{
outline = in_outline;
}
outline = outline.offset(infill_overlap);
if (line_distance > infill_line_width * 3 / 2)
{ // infill is not too dense to have overlap with surrounding polygon
outline = outline.offset(infill_overlap * infill_line_width / 100); // division by 100 cause it's a percentage.
}
if (outline.size() == 0)
{
+17 -4
Ver Arquivo
@@ -3,7 +3,7 @@
#define INFILL_H
#include "utils/polygon.h"
#include "settings/settings.h"
#include "settings.h"
// #include "ZigzagConnectorProcessor.h"
#include "infill/ZigzagConnectorProcessor.h"
#include "infill/NoZigZagConnectorProcessor.h"
@@ -23,18 +23,20 @@ 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
double infill_overlap; //!< the percentage (of infill_line_width) to overlap with the actual area within which to generate infill
double fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid)
bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
public:
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, 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, bool remove_overlapping_perimeters, int infill_line_width, int line_distance, double infill_overlap, double fill_angle, bool connected_zigzags = false, bool use_endpieces = false)
: pattern(pattern)
, in_outline(in_outline)
, outline_offset(outline_offset)
, remove_overlapping_perimeters(remove_overlapping_perimeters)
, infill_line_width(infill_line_width)
, line_distance(line_distance)
, infill_overlap(infill_overlap)
@@ -48,8 +50,9 @@ 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);
void generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between);
private:
@@ -61,6 +64,16 @@ 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
+74
Ver Arquivo
@@ -0,0 +1,74 @@
/** 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 nozzle_width, 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)
{
if (line_width_0 < nozzle_width)
{
PolygonUtils::offsetSafe(part->outline, - nozzle_width/2, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
}
else
{
PolygonUtils::offsetSafe(part->outline, - line_width_0/2, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
}
} else if (i == 1)
{
if (line_width_0 < nozzle_width)
{
int offset_from_first_boundary_for_edge_of_outer_wall = -nozzle_width/2;
// ideally this /\ should be: nozzle_width/2 - line_width_0; however, factually, the nozzle will fill up part of the perimeter gaps
PolygonUtils::offsetSafe(part->insets[0], nozzle_width/2 - line_width_0 - line_width_x/2, offset_from_first_boundary_for_edge_of_outer_wall, line_width_x, part->insets[1], &part->perimeterGaps, avoidOverlappingPerimeters);
}
else
{
PolygonUtils::offsetSafe(part->insets[0], -line_width_0/2 - line_width_x/2, -line_width_0/2, line_width_x, part->insets[1], &part->perimeterGaps, avoidOverlappingPerimeters);
}
} else
{
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 nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
{
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
{
generateInsets(&layer->parts[partNr], nozzle_width, line_width_0, line_width_x, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
}
//Remove the parts which did not generate an inset. As these parts are too small to print,
// 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
+41
Ver Arquivo
@@ -0,0 +1,41 @@
/** 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 nozzle_width The diameter of the hole in the nozzle
* \param line_width_0 line width of the outer wall
* \param line_width_x line width of other walls
* \param insetCount The number of insets to to generate
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
*/
void generateInsets(SliceLayerPart* part, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
/*!
* Generates the insets / perimeters for all parts in a layer.
*
* Note that the second inset gets offsetted by \p line_width_0 instead of the first,
* which leads to better results for a smaller \p line_width_0 than \p line_width_x and when printing the outer wall last.
*
* \param layer The layer for which to generate the insets.
* \param nozzle_width The diameter of the hole in the nozzle
* \param line_width_0 line width of the outer wall
* \param line_width_x line width of other walls
* \param insetCount The number of insets to to generate
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
*/
void generateInsets(SliceLayer* layer, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
}//namespace cura
#endif//INSET_H
+11 -11
Ver Arquivo
@@ -1,8 +1,8 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "layerPart.h"
#include "settings/settings.h"
#include "progress/Progress.h"
#include "settings.h"
#include "Progress.h"
#include "utils/SVG.h" // debug output
@@ -26,15 +26,15 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
if (union_all_remove_holes)
{
for(unsigned int i=0; i<layer->polygons.size(); i++)
for(unsigned int i=0; i<layer->polygonList.size(); i++)
{
if (layer->polygons[i].orientation())
layer->polygons[i].reverse();
if (layer->polygonList[i].orientation())
layer->polygonList[i].reverse();
}
}
std::vector<PolygonsPart> result;
result = layer->polygons.splitIntoParts(union_layers || union_all_remove_holes);
result = layer->polygonList.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& mesh, Slicer* slicer, bool union_layers, bool union_all_remove_holes)
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes)
{
for(unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
{
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);
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);
}
}
+2 -2
Ver Arquivo
@@ -22,9 +22,9 @@ namespace cura {
void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool union_layers, bool union_all_remove_holes);
void createLayerParts(SliceMeshStorage& mesh, Slicer* slicer, bool union_layers, bool union_all_remove_holes);
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes);
void layerparts2HTML(SliceDataStorage& mesh, const char* filename, bool all_layers = true, int layer_nr = -1);
void layerparts2HTML(SliceDataStorage& storage, const char* filename, bool all_layers = true, int layer_nr = -1);
}//namespace cura
+17 -95
Ver Arquivo
@@ -16,9 +16,7 @@
#include "utils/string.h"
#include "FffProcessor.h"
#include "settings/SettingRegistry.h"
#include "settings/SettingsToGV.h"
#include "settingRegistry.h"
namespace cura
{
@@ -30,25 +28,23 @@ 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.def.json>]\n");
cura::logError("CuraEngine connect <host>[:<port>] [-j <settings.json>]\n");
cura::logError(" --connect <host>[:<port>]\n\tConnect to <host> via a command socket, \n\tinstead of passing information via the command line\n");
cura::logError(" -j<settings.def.json>\n\tLoad settings.json file to register all settings and their defaults\n");
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults\n");
cura::logError("\n");
cura::logError("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e<extruder_nr>] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
cura::logError("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
cura::logError(" -v\n\tIncrease the verbose level (show log messages).\n");
cura::logError(" -p\n\tLog progress information.\n");
cura::logError(" -j\n\tLoad settings.def.json file to register all settings and their defaults.\n");
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults.\n");
cura::logError(" -s <setting>=<value>\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n");
cura::logError(" -l <model_file>\n\tLoad an STL model. \n");
cura::logError(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n");
cura::logError(" -e<extruder_nr>\n\tSwitch setting focus to the extruder train with the given number.\n");
cura::logError(" -e\n\tAdd a new extruder train.\n");
cura::logError(" --next\n\tGenerate gcode for the previously supplied mesh group and append that to \n\tthe gcode of further models for one-at-a-time printing.\n");
cura::logError(" -o <output_file>\n\tSpecify a file to which to write the generated gcode.\n");
cura::logError("\n");
cura::logError("The settings are appended to the last supplied object:\n");
cura::logError("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-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("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e [extruder train settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
cura::logError("\n");
}
@@ -70,10 +66,10 @@ void print_call(int argc, char **argv)
void connect(int argc, char **argv)
{
CommandSocket::instantiate();
std::string ip;
int port = 49674;
// parse ip port
std::string ip_port(argv[2]);
if (ip_port.find(':') != std::string::npos)
{
@@ -81,6 +77,7 @@ 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];
@@ -95,7 +92,7 @@ void connect(int argc, char **argv)
break;
case 'j':
argn++;
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn], FffProcessor::getInstance()))
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
{
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
}
@@ -109,8 +106,7 @@ void connect(int argc, char **argv)
}
}
}
CommandSocket::instantiate();
CommandSocket::getInstance()->connect(ip, port);
}
@@ -124,8 +120,7 @@ void slice(int argc, char **argv)
int extruder_train_nr = 0;
SettingsBase* last_extruder_train = meshgroup->createExtruderTrain(0);
// extruder defaults cannot be loaded yet cause no json has been parsed
SettingsBase* last_extruder_train = meshgroup->createExtruderTrain(0);
SettingsBase* last_settings_object = FffProcessor::getInstance();
for(int argn = 2; argn < argc; argn++)
{
@@ -144,8 +139,7 @@ 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
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, train);
meshgroup->getExtruderTrain(extruder_nr)->setExtruderTrainDefaults(extruder_nr); // create new extruder train objects or use already existing ones
}
//start slicing
FffProcessor::getInstance()->processMeshGroup(meshgroup);
@@ -156,7 +150,6 @@ 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");
@@ -178,7 +171,7 @@ void slice(int argc, char **argv)
break;
case 'j':
argn++;
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn], last_settings_object))
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
{
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
}
@@ -188,7 +181,6 @@ 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++;
@@ -248,11 +240,9 @@ 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++)
for (extruder_train_nr = 0; extruder_train_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_train_nr++)
{ // initialize remaining extruder trains and load the defaults
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_train_nr); // create new extruder train objects or use already existing ones
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_train_nr, train);
meshgroup->createExtruderTrain(extruder_train_nr)->setExtruderTrainDefaults(extruder_train_nr); // create new extruder train objects or use already existing ones
}
@@ -335,74 +325,6 @@ 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]);
+9 -39
Ver Arquivo
@@ -1,17 +1,11 @@
#include "mesh.h"
#include "utils/logoutput.h"
// #define LOG_MESH_ERRORS
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)
static inline uint32_t pointHash(Point3& p)
{
return ((p.x + vertex_meld_distance/2) / vertex_meld_distance) ^ (((p.y + vertex_meld_distance/2) / vertex_meld_distance) << 10) ^ (((p.z + vertex_meld_distance/2) / vertex_meld_distance) << 20);
}
@@ -61,21 +55,16 @@ void Mesh::finish()
}
}
Point3 Mesh::min() const
Point3 Mesh::min()
{
return aabb.min;
}
Point3 Mesh::max() const
Point3 Mesh::max()
{
return aabb.max;
}
AABB3D Mesh::getAABB() const
{
return aabb;
}
int Mesh::findIndexOfVertex(const Point3& v)
int Mesh::findIndexOfVertex(Point3& v)
{
uint32_t hash = pointHash(v);
@@ -118,7 +107,7 @@ See <a href="http://stackoverflow.com/questions/14066933/direct-way-of-computing
*/
int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx)
{
std::vector<int> candidateFaces; // in case more than two faces meet at an edge, multiple candidates are generated
int notFaceVertexIdx = -1; // index of the third vertex of the face corresponding to notFaceIdx
@@ -137,27 +126,14 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
) candidateFaces.push_back(f);
}
if (candidateFaces.size() == 0)
{
#ifdef LOG_MESH_ERRORS
cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx);
#endif
return -1;
}
if (candidateFaces.size() == 0) { cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx); return -1; }
if (candidateFaces.size() == 1) { return candidateFaces[0]; }
if (notFaceVertexIdx < 0)
{
#ifdef LOG_MESH_ERRORS
cura::logError("Couldn't find third point on face %i.\n", notFaceIdx);
#endif
return -1;
}
if (notFaceVertexIdx < 0) { cura::logError("Couldn't find third point on face %i.\n", notFaceIdx); return -1; }
#ifdef LOG_MESH_ERRORS
if (candidateFaces.size() % 2 == 0) cura::log("Warning! Edge with uneven number of faces connecting it!(%i)\n", candidateFaces.size()+1);
#endif
FPoint3 vn = vertices[idx1].p - vertices[idx0].p;
FPoint3 n = vn / vn.vSize(); // the normal of the plane in which all normals of faces connected to the edge lie => the normalized normal
@@ -166,9 +142,7 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
// the normals below are abnormally directed! : these normals all point counterclockwise (viewed from idx1 to idx0) from the face, irrespective of the direction of the face.
FPoint3 n0 = FPoint3(vertices[notFaceVertexIdx].p - vertices[idx0].p).cross(v0);
#ifdef LOG_MESH_ERRORS
if (n0.vSize() <= 0) cura::log("Warning! Face %i has zero area!", notFaceIdx);
#endif
double smallestAngle = 1000; // more then 2 PI (impossible angle)
int bestIdx = -1;
@@ -192,9 +166,7 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
if (angle == 0)
{
#ifdef LOG_MESH_ERRORS
cura::log("Warning! Overlapping faces: face %i and face %i.\n", notFaceIdx, candidateFace);
#endif
std::cerr<< n.vSize() <<"; "<<n1.vSize()<<";"<<n0.vSize() <<std::endl;
}
if (angle < smallestAngle)
@@ -203,10 +175,8 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
bestIdx = candidateFace;
}
}
#ifdef LOG_MESH_ERRORS
if (bestIdx < 0) cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx);
#endif
return bestIdx;
}
}//namespace cura
}//namespace cura
+6 -7
Ver Arquivo
@@ -1,8 +1,8 @@
#ifndef MESH_H
#define MESH_H
#include "settings/settings.h"
#include "utils/AABB3D.h"
#include "settings.h"
#include "utils/AABB.h"
namespace cura
{
@@ -69,9 +69,8 @@ public:
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
Point3 min(); //!< min (in x,y and z) vertex of the bounding box
Point3 max(); //!< max (in x,y and z) vertex of the bounding box
/*!
* Offset the whole mesh (all vertices and the bounding box).
@@ -86,12 +85,12 @@ public:
}
private:
int findIndexOfVertex(const Point3& v); //!< find index of vertex close to the given point, or create a new vertex and return its index.
int findIndexOfVertex(Point3& v); //!< find index of vertex close to the given point, or create a new vertex and return its index.
/*!
Get the index of the face connected to the face with index \p notFaceIdx, via vertices \p idx0 and \p idx1.
In case multiple faces connect with the same edge, return the next counter-clockwise face when viewing from \p idx1 to \p idx0.
*/
int getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const;
int getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx);
};
}//namespace cura
+19 -49
Ver Arquivo
@@ -6,29 +6,15 @@ 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++)
for(unsigned int idx=0; idx < volumes.size(); idx++)
{
Slicer& volume_1 = *volumes[volume_1_idx];
if (volume_1.mesh->getSettingBoolean("infill_mesh"))
for(unsigned int idx2=0; idx2<idx; idx2++)
{
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"))
for(unsigned int layerNr=0; layerNr < volumes[idx]->layers.size(); layerNr++)
{
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);
SlicerLayer& layer1 = volumes[idx]->layers[layerNr];
SlicerLayer& layer2 = volumes[idx2]->layers[layerNr];
layer1.polygonList = layer1.polygonList.difference(layer2.polygonList);
}
}
}
@@ -36,40 +22,24 @@ void carveMultipleVolumes(std::vector<Slicer*> &volumes)
//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)
void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes, int overlap)
{
if (volumes.size() < 2)
if (volumes.size() < 2 || overlap <= 0) return;
for(unsigned int layerNr=0; layerNr < volumes[0]->layers.size(); layerNr++)
{
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)
Polygons fullLayer;
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
{
continue;
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?)
}
for (unsigned int layer_nr = 0; layer_nr < volume->layers.size(); layer_nr++)
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++)
{
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)));
SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr];
layer1.polygonList = fullLayer.intersection(layer1.polygonList.offset(overlap / 2));
}
}
}
+1 -1
Ver Arquivo
@@ -13,7 +13,7 @@ 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);
void generateMultipleVolumesOverlap(std::vector<Slicer*> &meshes, int overlap);
}//namespace cura
+183 -135
Ver Arquivo
@@ -2,30 +2,32 @@
#include "pathOrderOptimizer.h"
#include "utils/logoutput.h"
#include "utils/BucketGrid2D.h"
#include "utils/linearAlg2D.h"
#include "utils/TravellingSalesman.h"
#define INLINE static inline
namespace cura {
namespace cura
{
/**
*
*/
*
*/
void PathOrderOptimizer::optimize()
{
bool picked[polygons.size()];
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
for (PolygonRef poly : polygons) /// find closest point to initial starting point within each polygon +initialize picked
memset(picked, false, sizeof (bool) * polygons.size()); /// initialized as falses
for (unsigned int i_polygon = 0; i_polygon < polygons.size(); i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
{
int best = -1;
float bestDist = std::numeric_limits<float>::infinity();
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++) /// get closest point in polygon
PolygonRef poly = polygons[i_polygon];
for (unsigned int i_point = 0; i_point < poly.size(); i_point++) /// get closest point in polygon
{
float dist = vSize2f(poly[point_idx] - startPoint);
float dist = vSize2f(poly[i_point] - startPoint);
if (dist < bestDist)
{
best = point_idx;
best = i_point;
bestDist = dist;
}
}
@@ -37,50 +39,46 @@ void PathOrderOptimizer::optimize()
Point prev_point = startPoint;
for (unsigned int poly_order_idx = 0; poly_order_idx < polygons.size(); poly_order_idx++) /// actual path order optimizer
for (unsigned int polygon_idx = 0; polygon_idx < polygons.size(); polygon_idx++) /// actual path order optimizer
{
int best_poly_idx = -1;
int best = -1;
float bestDist = std::numeric_limits<float>::infinity();
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
for (unsigned int polygon2_idx = 0; polygon2_idx < polygons.size(); polygon2_idx++)
{
if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons
{
if (picked[polygon2_idx] || polygons[polygon2_idx].size() < 1) /// skip single-point-polygons
continue;
}
assert (polygons[poly_idx].size() != 2);
assert(polygons[polygon2_idx].size() != 2);
float dist = vSize2f(polygons[poly_idx][polyStart[poly_idx]] - prev_point);
float dist = vSize2f(polygons[polygon2_idx][polyStart[polygon2_idx]] - prev_point);
if (dist < bestDist)
{
best_poly_idx = poly_idx;
best = polygon2_idx;
bestDist = dist;
}
}
if (best_poly_idx > -1) /// should always be true; we should have been able to identify the best next polygon
if (best > -1) /// should always be true; we should have been able to identify the best next polygon
{
assert(polygons[best_poly_idx].size() != 2);
assert(polygons[best].size() != 2);
prev_point = polygons[best_poly_idx][polyStart[best_poly_idx]];
prev_point = polygons[best][polyStart[best]];
picked[best_poly_idx] = true;
polyOrder.push_back(best_poly_idx);
picked[best] = true;
polyOrder.push_back(best);
}
else
{
logError("Failed to find next closest polygon.\n");
}
}
prev_point = startPoint;
for (unsigned int order_idx = 0; order_idx < polyOrder.size(); order_idx++) /// decide final starting points in each polygon
for (unsigned int n = 0; n < polyOrder.size(); n++) /// decide final starting points in each polygon
{
int poly_idx = polyOrder[order_idx];
int poly_idx = polyOrder[n];
int point_idx = getPolyStart(prev_point, poly_idx);
polyStart[poly_idx] = point_idx;
prev_point = polygons[poly_idx][point_idx];
@@ -92,34 +90,32 @@ int PathOrderOptimizer::getPolyStart(Point prev_point, int poly_idx)
{
switch (type)
{
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
case EZSeamType::SHORTEST: return getClosestPointInPolygon(prev_point, poly_idx);
default: return getClosestPointInPolygon(prev_point, poly_idx);
}
}
int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int poly_idx)
{
PolygonRef poly = polygons[poly_idx];
int best_point_idx = -1;
float best_point_score = std::numeric_limits<float>::infinity();
Point p0 = poly.back();
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
float bestDist = std::numeric_limits<float>::infinity();
bool orientation = poly.orientation();
for (unsigned int i_point = 0; i_point < poly.size(); i_point++)
{
Point& p1 = poly[point_idx];
Point& p2 = poly[(point_idx + 1) % poly.size()];
int64_t dist = vSize2(p1 - prev_point);
float is_on_inside_corner_score = -LinearAlg2D::getAngleLeft(p0, p1, p2) / M_PI * 5000 * 5000; // prefer inside corners
// this score is in the order of 5 mm
if (dist + is_on_inside_corner_score < best_point_score)
float dist = vSize2f(poly[i_point] - prev_point);
Point n0 = normal(poly[(i_point - 1 + poly.size()) % poly.size()] - poly[i_point], 2000);
Point n1 = normal(poly[i_point] - poly[(i_point + 1) % poly.size()], 2000);
float dot_score = dot(n0, n1) - dot(crossZ(n0), n1); /// prefer binnenbocht
if (orientation)
dot_score = -dot_score;
if (dist + dot_score < bestDist)
{
best_point_idx = point_idx;
best_point_score = dist + is_on_inside_corner_score;
best_point_idx = i_point;
bestDist = dist;
}
p0 = p1;
}
return best_point_idx;
}
@@ -129,13 +125,12 @@ int PathOrderOptimizer::getRandomPointInPolygon(int poly_idx)
return rand() % polygons[poly_idx].size();
}
int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
{
PolygonRef poly = polygons[poly_idx];
int best_point_idx = -1;
float best_y = std::numeric_limits<float>::min();
for(unsigned int point_idx=0 ; point_idx<poly.size() ; point_idx++)
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
if (poly[point_idx].Y > best_y)
{
@@ -146,124 +141,177 @@ int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
return best_point_idx;
}
LineOrderOptimizer::LineOrderOptimizer(const Point& start_point, unsigned long long cluster_grid_size)
: cluster_grid_size(cluster_grid_size == 0 ? 2000 : cluster_grid_size) //Initialise cluster_grid_size to 2000 if the input grid size is invalid (e.g. no infill).
{
this->startPoint = start_point;
}
/**
*
*/
*
*/
void LineOrderOptimizer::optimize()
{
int gridSize = 5000; // the size of the cells in the hash grid. TODO
BucketGrid2D<unsigned int> line_bucket_grid(gridSize);
bool picked[polygons.size()];
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) /// find closest point to initial starting point within each polygon +initialize picked
if (lines.empty()) //Nothing to do. Terminate early.
{
int best_point_idx = -1;
float best_point_dist = std::numeric_limits<float>::infinity();
PolygonRef poly = polygons[poly_idx];
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++) /// get closest point from polygon
{
float dist = vSize2f(poly[point_idx] - startPoint);
if (dist < best_point_dist)
{
best_point_idx = point_idx;
best_point_dist = dist;
}
}
polyStart.push_back(best_point_idx);
assert(poly.size() == 2);
line_bucket_grid.insert(poly[0], poly_idx);
line_bucket_grid.insert(poly[1], poly_idx);
return;
}
//Since polyOrder must be filled with indices, an index in the polygons vector represents each line.
std::vector<Cluster> line_clusters = cluster();
Point incoming_perpundicular_normal(0, 0);
Point prev_point = startPoint;
for (unsigned int order_idx = 0; order_idx < polygons.size(); order_idx++) /// actual path order optimizer
//Define how the TSP solver should use its elements.
std::function<std::vector<std::pair<Point, Point>> (size_t)> get_orientations = [&](size_t cluster_index)->std::vector<std::pair<Point, Point>> //How to get the possible orientations of a cluster.
{
int best_line_idx = -1;
float best_score = std::numeric_limits<float>::infinity(); // distance score for the best next line
for(unsigned int close_line_poly_idx : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
std::vector<std::pair<Point, Point>> result;
const size_t first_line_index = line_clusters[cluster_index][0]; //The first line in the current cluster.
const size_t last_line_index = line_clusters[cluster_index].back(); //The last line in the current cluster.
const PolygonRef first_line = lines[first_line_index];
const PolygonRef last_line = lines[last_line_index];
const Point start_normal = first_line[polyStart[first_line_index]]; //Start of the path, not mirrored.
const Point end_normal = last_line[(polyStart[last_line_index] + last_line.size() - 1) % last_line.size()]; //End of the path, not mirrored.
result.push_back(std::pair<Point, Point>(start_normal, end_normal));
result.push_back(std::pair<Point, Point>(end_normal, start_normal)); //Can also insert in reverse!
if (line_clusters[cluster_index].size() > 1u) //If the cluster has one line, mirroring the line is equal to reversing the path. Otherwise, we must also include mirrored options.
{
if (picked[close_line_poly_idx] || polygons[close_line_poly_idx].size() < 1)
{
continue;
}
updateBestLine(close_line_poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
const Point start_mirrored = first_line[(polyStart[first_line_index] + first_line.size() - 1) % first_line.size()]; //Start of the path, mirrored.
const Point end_mirrored = first_line[(polyStart[first_line_index] + first_line.size() - 1) % first_line.size()]; //End of the path, mirrored.
result.push_back(std::pair<Point, Point>(start_mirrored, end_mirrored));
result.push_back(std::pair<Point, Point>(end_mirrored, start_mirrored));
}
return result;
};
TravellingSalesman<size_t> tspsolver(get_orientations); //Solves the macro TSP problem of ordering the clusters.
std::vector<size_t> cluster_orientations;
std::vector<size_t> unoptimised(line_clusters.size());
std::iota(unoptimised.begin(), unoptimised.end(), 0);
std::vector<size_t> optimised = tspsolver.findPath(unoptimised, cluster_orientations, &startPoint); //Approximate the shortest path with the TSP solver.
if (best_line_idx == -1) /// if single-line-polygon hasn't been found yet
//Actually put the paths in their correct order for the output.
polyOrder.reserve(lines.size());
for (size_t cluster_index = 0; cluster_index < optimised.size(); cluster_index++)
{
Cluster cluster = line_clusters[optimised[cluster_index]];
//Determine in what orientation we should place the cluster depending on cluster_orientations[cluster_index].
size_t orientation = cluster_orientations[cluster_index];
if (cluster.size() == 1) //Singleton clusters have only 2 possible orientations.
{
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
polyOrder.push_back(static_cast<int>(cluster[0]));
if (orientation >= 1)
{
if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons
polyStart[cluster[0]] = 1 - polyStart[cluster[0]];
}
}
else //Larger clusters have 4 possible orientations.
{
if ((orientation & 1) == 0) //Not reversed.
{
for (size_t polygon_index = 0; polygon_index < cluster.size(); polygon_index++)
{
polyOrder.push_back(static_cast<int>(cluster[polygon_index]));
}
}
else //Reversed.
{
for (size_t polygon_index = cluster.size(); polygon_index-- > 0; ) //Insert the lines in backward direction.
{
polyOrder.push_back(static_cast<int>(cluster[polygon_index]));
}
}
if (orientation >= 2u) //Mirrored.
{
for (size_t polygon_index : cluster) //Mirror each line in the cluster.
{
polyStart[polygon_index] = 1 - polyStart[polygon_index];
}
}
}
}
}
std::vector<std::vector<size_t>> LineOrderOptimizer::cluster()
{
polyStart.resize(lines.size()); //Polystart should always contain an entry for all polygons.
BucketGrid2D<size_t> grid(cluster_grid_size);
for (size_t polygon_index = 0; polygon_index < lines.size(); polygon_index++) //First put every endpoint of all lines in the grid.
{
grid.insert(lines[polygon_index][0], polygon_index);
grid.insert(lines[polygon_index].back(), polygon_index);
}
std::vector<Cluster> clusters;
bool picked[lines.size()]; //For each polygon, whether it is already in a cluster.
memset(picked, 0, lines.size()); //Initialise to false.
for (size_t polygon_index = 0; polygon_index < lines.size(); polygon_index++) //Find clusters with nearest neighbour-ish search.
{
if (picked[polygon_index]) //Already in a cluster.
{
continue;
}
clusters.push_back(Cluster()); //Make a new cluster for this line.
clusters.back().push_back(polygon_index);
polyStart[polygon_index] = 0; //Choose one possible mirroring of the lines. This determines the start point of each line. The mirror of a cluster is also checked by the TSP solver (but the combination of directions of each individual line is determined here below).
picked[polygon_index] = true;
size_t current_polygon = polygon_index; //We'll do a walk to the nearest valid neighbour. A neighbour is valid if it is not picked yet and if both its endpoints are near.
size_t best_polygon = current_polygon;
while (best_polygon != static_cast<size_t>(-1)) //Keep going until there is no valid neighbour.
{
const PolygonRef current_line = lines[current_polygon];
Point current_start = current_line[polyStart[current_polygon]]; //Start and end point of the current polygon. These are used to find the distance to the next polygon.
Point current_end = current_line[(polyStart[current_polygon] + current_line.size() - 1) % current_line.size()];
best_polygon = static_cast<size_t>(-1);
unsigned long long best_distance = cluster_grid_size * cluster_grid_size + 1; //grid_size squared since vSize2 gives squared distance.
size_t best_start;
for (size_t neighbour : grid.findNearbyObjects(current_line[0]))
{
if (picked[neighbour]) //Don't use neighbours that are already in another cluster.
{
continue;
}
assert(polygons[poly_idx].size() == 2);
updateBestLine(poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
tryCluster(neighbour, current_start, current_end, &best_polygon, &best_distance, &best_start);
}
if (best_polygon != static_cast<size_t>(-1)) //We found one.
{
current_polygon = best_polygon;
clusters.back().push_back(best_polygon);
polyStart[best_polygon] = best_start;
picked[best_polygon] = true;
}
}
if (best_line_idx > -1) /// should always be true; we should have been able to identify the best next polygon
{
PolygonRef best_line = polygons[best_line_idx];
assert(best_line.size() == 2);
int line_start_point_idx = polyStart[best_line_idx];
int line_end_point_idx = line_start_point_idx * -1 + 1; /// 1 -> 0 , 0 -> 1
Point& line_start = best_line[line_start_point_idx];
Point& line_end = best_line[line_end_point_idx];
prev_point = line_end;
incoming_perpundicular_normal = turn90CCW(normal(line_end - line_start, 1000));
picked[best_line_idx] = true;
polyOrder.push_back(best_line_idx);
}
else
{
logError("Failed to find next closest line.\n");
}
}
return clusters;
}
inline void LineOrderOptimizer::updateBestLine(unsigned int poly_idx, int& best, float& best_score, Point prev_point, Point incoming_perpundicular_normal)
void LineOrderOptimizer::tryCluster(size_t line, const Point current_start, const Point current_end, size_t* best_polygon, unsigned long long* best_distance, size_t* best_start)
{
Point& p0 = polygons[poly_idx][0];
Point& p1 = polygons[poly_idx][1];
float dot_score = getAngleScore(incoming_perpundicular_normal, p0, p1);
{ /// check distance to first point on line (0)
float score = vSize2f(p0 - prev_point) + dot_score; // prefer 90 degree corners
if (score < best_score)
//Input checking.
if (!best_polygon || !best_distance || !best_start) //Output parameter missing.
{
return;
}
const unsigned long long distance_start_start = vSize2(current_start - lines[line][0]);
const unsigned long long distance_start_end = vSize2(current_start - lines[line].back());
const unsigned long long distance_end_start = vSize2(current_end - lines[line][0]);
const unsigned long long distance_end_end = vSize2(current_end - lines[line].back());
if (distance_start_start < cluster_grid_size * cluster_grid_size && distance_end_end < cluster_grid_size * cluster_grid_size) //Two lines are alongside each other.
{
if (distance_end_end < *best_distance) //Best neighbour and orientation so far.
{
best = poly_idx;
best_score = score;
polyStart[poly_idx] = 0;
*best_polygon = line;
*best_distance = distance_end_end;
*best_start = lines[line].size() - 1;
}
}
{ /// check distance to second point on line (1)
float score = vSize2f(p1 - prev_point) + dot_score; // prefer 90 degree corners
if (score < best_score)
if (distance_start_end < cluster_grid_size * cluster_grid_size && distance_end_start < cluster_grid_size * cluster_grid_size) //Two lines are alongside each other, but in reverse direction.
{
if (distance_end_start < *best_distance)
{
best = poly_idx;
best_score = score;
polyStart[poly_idx] = 1;
*best_polygon = line;
*best_distance = distance_end_start;
*best_start = 0;
}
}
}
float LineOrderOptimizer::getAngleScore(Point incoming_perpundicular_normal, Point p0, Point p1)
{
return dot(incoming_perpundicular_normal, normal(p1 - p0, 1000)) * 0.0001f;
}
}//namespace cura
+60 -31
Ver Arquivo
@@ -4,7 +4,7 @@
#include <stdint.h>
#include "utils/polygon.h"
#include "settings/settings.h"
#include "settings.h"
namespace cura {
@@ -56,60 +56,89 @@ private:
*/
class LineOrderOptimizer
{
typedef typename std::vector<size_t> Cluster; //To make it more clear what a cluster is.
public:
/*!
* \brief The size of the grid cells used to cluster lines.
*
* Increase this value to make the optimisation algorithm fall back to
* nearest neighbour more often. Reduce this value to make the optimisation
* algorithm use random insertion on smaller pieces of the input.
*/
const unsigned long long cluster_grid_size;
Point startPoint; //!< The location of the nozzle before starting to print the current layer
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
std::vector<PolygonRef> lines; //!< the parts of the layer (in arbitrary order)
std::vector<int> polyStart; //!< polygons[i][polyStart[i]] = point of polygon i which is to be the starting point in printing the polygon
std::vector<int> polyOrder; //!< the optimized order as indices in #polygons
LineOrderOptimizer(Point startPoint)
{
this->startPoint = startPoint;
}
/*!
* \brief Constructs the line order optimiser with the specified settings.
*
* \param start_point The starting point from where the paths generated by
* this optimiser must start.
* \param cluster_grid_size The size of the grid cells used to cluster
* lines. Make this bigger and the optimiser will fall back to nearest
* neighbour search more often. Make this smaller and the optimiser will
* use random insertion more often.
*/
LineOrderOptimizer(const Point& start_point, unsigned long long cluster_grid_size);
void addPolygon(PolygonRef polygon)
{
this->polygons.push_back(polygon);
this->lines.push_back(polygon);
}
void addPolygons(Polygons& polygons)
{
for(unsigned int i=0;i<polygons.size(); i++)
this->polygons.push_back(polygons[i]);
for(unsigned int i = 0; i < polygons.size(); i++)
{
this->lines.push_back(polygons[i]);
}
}
void optimize(); //!< sets #polyStart and #polyOrder
private:
/*!
* Update LineOrderOptimizer::polyStart if the current line is better than the current best.
* \brief Clusters the polygons in groups such that the start and end of the
* polygons in each group are close together.
*
* Besides looking at the distance from the previous line segment, we also look at the angle we make.
* This performs a simple nearest-neighbour traversal through all lines. An
* arbitrary line is chosen as starting point for a cluster, and iteratively
* the nearest neighbouring line will get added to that cluster. A line is
* only neighbouring if both of its endpoints are nearby the endpoints of
* the previous line. This way you get logical groups of lines that should
* always be in sequence, with fairly low computational cost.
*
* We prefer 90 degree angles; 180 degree turn arounds are slow on machines where the jerk is limited.
* 0 degree (straight ahead) 'corners' occur only when a single infill line is interrupted,
* in which case the travel move might involve combing, which makes it rather longer.
*
* \param poly_idx[in] The index in LineOrderOptimizer::polygons for the current line to test
* \param best[in, out] The index of current best line
* \param best_score[in, out] The distance score for the current best line
* \param prev_point[in] The previous point from which to find the next best line
* \param incoming_perpundicular_normal[in] The direction of movement when the print head arrived at \p prev_point, turned 90 degrees CCW
* \return Clusters of polygons, where each cluster is represented with a
* vector of indices pointing to positions in \link polygons.
*/
void updateBestLine(unsigned int poly_idx, int& best, float& best_score, Point prev_point, Point incoming_perpundicular_normal);
std::vector<Cluster> cluster();
/*!
* Get a score to modify the distance score for measuring how good two lines follow each other.
*
* The angle score is symmetric in \p from and \p to; they can be exchanged without altering the result. (Code relies on this property)
*
* \param incoming_perpundicular_normal The direction in which the head was moving while printing the previous line, turned 90 degrees CCW
* \param from The one end of the next line
* \param to The other end of the next line
* \return A score measuring how good the angle is of the line between \p from and \p to when the previous line had a direction given by \p incoming_perpundicular_normal
*
* \brief Tries to insert the specified line in the currently processed
* cluster.
*
* The distance of the line to the current line (represented by
* \p current_start and \p current_end) is measured, and compared to the
* distance of the best candidate to insert so far. If it is shorter, then
* the new line is stored via \p best_polygon as the best line to add to the
* cluster.
*
* \param line The index of the new line to try to add to the cluster.
* \param current_start The start point of the previous line that was added.
* \param current_end The end point of the previous line that was added.
* \param best_polygon The line that is the best candidate to insert so far.
* This parameter may be changed by this function if the new line is better.
* \param best_distance The distance of the best candidate to insert so far.
* This parameter may be changed by this function if the new line is better.
* \param best_start The starting vertex index of the best candidate to
* insert so far. This parameter may be changed by this function if the new
* line is better.
*/
static float getAngleScore(Point incoming_perpundicular_normal, Point from, Point to);
void tryCluster(size_t line, const Point current_start, const Point current_end, size_t* best_polygon, unsigned long long* best_distance, size_t* best_start);
};
}//namespace cura
-336
Ver Arquivo
@@ -1,336 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "Comb.h"
#include <algorithm>
#include "../utils/polygonUtils.h"
#include "../sliceDataStorage.h"
#include "../utils/SVG.h"
namespace cura {
// boundary_outside is only computed when it's needed!
Polygons& Comb::getBoundaryOutside()
{
if (!boundary_outside)
{
boundary_outside = new Polygons();
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
}
return *boundary_outside;
}
BucketGrid2D<PolygonsPointIndex>& Comb::getOutsideLocToLine()
{
Polygons& outside = getBoundaryOutside();
if (!outside_loc_to_line)
{
outside_loc_to_line = PolygonUtils::createLocToLineGrid(outside, offset_from_inside_to_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)
, layer_nr(layer_nr)
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
, max_moveInside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
, offset_from_outlines_outside(travel_avoid_distance)
, offset_from_inside_to_outside(offset_from_outlines + offset_from_outlines_outside)
, max_crossing_dist2(offset_from_inside_to_outside * offset_from_inside_to_outside * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation
, avoid_other_parts(travel_avoid_other_parts)
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
, boundary_inside( comb_boundary_inside )
, boundary_outside(nullptr)
, outside_loc_to_line(nullptr)
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
{
}
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, bool via_outside_makes_combing_fail, bool fail_on_unavoidable_obstacles)
{
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored))
{
return true;
}
//Move start and end point inside the comb boundary
unsigned int start_inside_poly = NO_INDEX;
const bool startInside = moveInside(_startInside, startPoint, start_inside_poly);
unsigned int end_inside_poly = NO_INDEX;
const bool endInside = moveInside(_endInside, endPoint, end_inside_poly);
unsigned int start_part_boundary_poly_idx;
unsigned int end_part_boundary_poly_idx;
unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx);
unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx);
if (startInside && endInside && start_part_idx == end_part_idx)
{ // normal combing within part
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
combPaths.emplace_back();
return LinePolygonsCrossings::comb(part, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
}
else
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
// 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
if (via_outside_makes_combing_fail)
{
return false;
}
Crossing start_crossing(startPoint, startInside, start_part_idx, start_part_boundary_poly_idx, boundary_inside);
Crossing end_crossing(endPoint, endInside, end_part_idx, end_part_boundary_poly_idx, boundary_inside);
{ // find crossing over the in-between area between inside and outside
start_crossing.findCrossingInOrMid(partsView_inside, endPoint);
end_crossing.findCrossingInOrMid(partsView_inside, start_crossing.in_or_mid);
}
bool avoid_other_parts_now = avoid_other_parts;
if (avoid_other_parts_now && vSize2(start_crossing.in_or_mid - end_crossing.in_or_mid) < offset_from_inside_to_outside * offset_from_inside_to_outside * 4)
{ // 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
bool success = start_crossing.findOutside(outside, end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this);
if (!success)
{
return false;
}
success = end_crossing.findOutside(outside, start_crossing.out, fail_on_unavoidable_obstacles, *this);
if (!success)
{
return false;
}
}
// generate the actual comb paths
if (startInside)
{
// start to boundary
assert(start_crossing.dest_part.size() > 0 && "The part we start inside when combing should have been computed already!");
combPaths.emplace_back();
bool combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, startPoint, start_crossing.in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
if (!combing_succeeded)
{ // Couldn't comb between start point and computed crossing from the start part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
return false;
}
}
// throught air from boundary to boundary
if (avoid_other_parts_now)
{
combPaths.emplace_back();
combPaths.throughAir = true;
if ( vSize(start_crossing.in_or_mid - end_crossing.in_or_mid) < vSize(start_crossing.in_or_mid - start_crossing.out) + vSize(end_crossing.in_or_mid - end_crossing.out) )
{ // via outside is moving more over the in-between zone
combPaths.back().push_back(start_crossing.in_or_mid);
combPaths.back().push_back(end_crossing.in_or_mid);
}
else
{
bool combing_succeeded = LinePolygonsCrossings::comb(getBoundaryOutside(), start_crossing.out, end_crossing.out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
if (!combing_succeeded)
{
return false;
}
}
}
else
{ // directly through air (not avoiding other parts)
combPaths.emplace_back();
combPaths.throughAir = true;
combPaths.back().cross_boundary = true; // TODO: calculate whether we cross a boundary!
combPaths.back().push_back(start_crossing.in_or_mid);
combPaths.back().push_back(end_crossing.in_or_mid);
}
if (endInside)
{
// boundary to end
assert(end_crossing.dest_part.size() > 0 && "The part we end up inside when combing should have been computed already!");
combPaths.emplace_back();
bool combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, end_crossing.in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
if (!combing_succeeded)
{ // Couldn't comb between end point and computed crossing to the end part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
return false;
}
}
return true;
}
}
Comb::Crossing::Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside)
: dest_is_inside(dest_is_inside)
, dest_crossing_poly(boundary_inside[dest_part_boundary_crossing_poly_idx]) // initialize with most obvious poly, cause mostly a combing move will move outside the part, rather than inside a hole in the part
, dest_point(dest_point)
, dest_part_idx(dest_part_idx)
{
}
bool Comb::moveInside(bool is_inside, Point& dest_point, unsigned int& inside_poly)
{
if (is_inside)
{
ClosestPolygonPoint cpp = PolygonUtils::ensureInsideOrOutside(boundary_inside, dest_point, offset_extra_start_end, max_moveInside_distance2);
if (cpp.point_idx == NO_INDEX)
{
return false;
}
else
{
inside_poly = cpp.poly_idx;
return true;
}
}
return false;
}
void Comb::Crossing::findCrossingInOrMid(const PartsView& partsView_inside, const Point close_to)
{
if (dest_is_inside)
{ // in-case
// find the point on the start inside-polygon closest to the endpoint, but also kind of close to the start point
Point _dest_point(dest_point); // copy to local variable for lambda capture
std::function<int(Point)> close_towards_start_penalty_function([_dest_point](Point candidate){ return vSize2((candidate - _dest_point) / 10); });
dest_part = partsView_inside.assemblePart(dest_part_idx);
Point result(close_to);
int64_t max_dist2 = std::numeric_limits<int64_t>::max();
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, offset_dist_to_get_from_on_the_polygon_to_outside, max_dist2, close_towards_start_penalty_function);
if (crossing_1_in_cp.point_idx != NO_INDEX)
{
dest_crossing_poly = crossing_1_in_cp.poly;
in_or_mid = result;
}
else
{ // part is too small to be ensuring a point inside with the given distance
in_or_mid = dest_point; // just use the startPoint or endPoint itself
}
}
else
{
in_or_mid = dest_point; // mid-case
}
};
bool Comb::Crossing::findOutside(const Polygons& outside, const Point close_to, const bool fail_on_unavoidable_obstacles, Comb& comber)
{
out = in_or_mid;
if (dest_is_inside || outside.inside(in_or_mid, true)) // start in_between
{ // move outside
Point preferred_crossing_1_out = in_or_mid + normal(close_to - in_or_mid, comber.offset_from_inside_to_outside);
std::function<int(Point)> close_to_penalty_function([preferred_crossing_1_out](Point candidate){ return vSize2((candidate - preferred_crossing_1_out) / 2); });
std::optional<ClosestPolygonPoint> crossing_1_out_cpp = PolygonUtils::findClose(in_or_mid, outside, comber.getOutsideLocToLine(), close_to_penalty_function);
if (crossing_1_out_cpp)
{
out = PolygonUtils::moveOutside(*crossing_1_out_cpp, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
}
else
{
PolygonUtils::moveOutside(outside, out, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
}
}
int64_t in_out_dist2_1 = vSize2(out - in_or_mid);
if (dest_is_inside && in_out_dist2_1 > comber.max_crossing_dist2) // moveInside moved too far
{ // if move is too far over in_between
// find crossing closer by
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(outside, dest_crossing_poly, dest_point, close_to, comber);
if (best)
{
in_or_mid = PolygonUtils::moveInside(best->first, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
out = PolygonUtils::moveOutside(best->second, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
}
if (fail_on_unavoidable_obstacles && vSize2(out - in_or_mid) > comber.max_crossing_dist2) // moveInside moved still too far
{
return false;
}
}
return true;
}
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> Comb::Crossing::findBestCrossing(const Polygons& outside, const PolygonRef from, const Point estimated_start, const Point estimated_end, Comb& comber)
{
ClosestPolygonPoint* best_in = nullptr;
ClosestPolygonPoint* best_out = nullptr;
int64_t best_detour_score = std::numeric_limits<int64_t>::max();
int64_t best_crossing_dist2;
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> crossing_out_candidates = PolygonUtils::findClose(from, outside, comber.getOutsideLocToLine());
bool seen_close_enough_connection = false;
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 > comber.max_crossing_dist2 * 2)
{ // preliminary filtering
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;
int64_t detour_score = crossing_dist2 + detour_dist * detour_dist / 1000; // prefer a closest connection over a detour
// The detour distance is generally large compared to the crossing distance.
// While the crossing is generally about 1mm across,
// the distance between an arbitrary point and the boundary may well be a couple of centimetres.
// So the crossing_dist2 is about 1.000.000 while the detour_dist_2 is in the order of 400.000.000
// In the end we just want to choose between two points which have the _same_ crossing distance, modulo rounding error.
if ((!seen_close_enough_connection && detour_score < best_detour_score) // keep the best as long as we havent seen one close enough (so that we may walk along the polygon to find a closer connection from it in the code below)
|| (!seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2) // make the one which is close enough the best as soon as we see one close enough
|| (seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2 && detour_score < best_detour_score)) // update to keep the best crossing which is close enough already
{
if (!seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2)
{
seen_close_enough_connection = true;
}
best_in = &crossing_candidate.first;
best_out = &crossing_candidate.second;
best_detour_score = detour_score;
best_crossing_dist2 = crossing_dist2;
}
}
if (best_detour_score == std::numeric_limits<int64_t>::max())
{ // i.e. if best_in == nullptr or if best_out == nullptr
return std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>();
}
if (best_crossing_dist2 > comber.max_crossing_dist2)
{ // find closer point on line segments, rather than moving between vertices of the polygons only
PolygonUtils::walkToNearestSmallestConnection(*best_in, *best_out);
best_crossing_dist2 = vSize2(best_in->location - best_out->location);
if (best_crossing_dist2 > comber.max_crossing_dist2)
{
return std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>();
}
}
return std::make_shared<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>(*best_in, *best_out);
}
}//namespace cura
-169
Ver Arquivo
@@ -1,169 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef PATH_PLANNING_COMB_H
#define PATH_PLANNING_COMB_H
#include <memory> // shared_ptr
#include "../utils/polygon.h"
#include "../utils/BucketGrid2D.h"
#include "../utils/polygonUtils.h"
#include "LinePolygonsCrossings.h"
#include "CombPath.h"
#include "CombPaths.h"
namespace cura
{
class SliceDataStorage;
/*!
* Class for generating a full combing actions from a travel move from a start point to an end point.
* A single Comb object is used for each layer.
*
* Comb::calc is the main function of this class.
*
* Typical output: A combing path to the boundary of the polygon + a move through air avoiding other parts in the layer + a combing path from the boundary of the ending polygon to the end point.
* Each of these three is a CombPath; the first and last are within Comb::boundary_inside while the middle is outside of Comb::boundary_outside.
* Between these there is a little gap where the nozzle crosses the boundary of an object approximately perpendicular to its boundary.
*
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
* because generally there is only one outside part; encapsulated holes occur less often.
*/
class Comb
{
friend class LinePolygonsCrossings;
private:
/*!
* A crossing from the inside boundary to the outside boundary.
*
* 'dest' is either the startPoint or the endpoint of a whole combing move.
*/
class Crossing
{
public:
bool dest_is_inside; //!< Whether the startPoint or endPoint is inside the inside boundary
Point in_or_mid; //!< The point on the inside boundary, or in between the inside and outside boundary if the start/end point isn't inside the inside boudary
Point out; //!< The point on the outside boundary
PolygonsPart dest_part; //!< The assembled inside-boundary PolygonsPart in which the dest_point lies. (will only be initialized when Crossing::dest_is_inside holds)
PolygonRef dest_crossing_poly; //!< The polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon)
/*!
* Simple constructor
*
* \param dest_point Either the eventual startPoint or the eventual endPoint of this combing move.
* \param dest_is_inside Whether the startPoint or endPoint is inside the inside boundary.
* \param dest_part_idx The index into Comb:partsView_inside of the part in which the \p dest_point is.
* \param dest_part_boundary_crossing_poly_idx The index in \p boundary_inside of the polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon).
* \param boundary_inside The boundary within which to comb.
*/
Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside);
/*!
* Find the not-outside location (Combing::in_or_mid) of the crossing between to the outside boundary
*
* \param partsView_inside Structured indices onto Comb::boundary_inside which shows which polygons belong to which part.
* \param close_to[in] Try to get a crossing close to this point
*/
void findCrossingInOrMid(const PartsView& partsView_inside, const Point close_to);
/*!
* Find the outside location (Combing::out)
*
* \param outside The outside boundary polygons
* \param close_to A point to get closer to when there are multiple candidates on the outside boundary which are almost equally close to the Crossing::in_or_mid
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
* \param comber[in] The combing calculator which has references to the offsets and boundaries to use in combing.
*/
bool findOutside(const Polygons& outside, const Point close_to, const bool fail_on_unavoidable_obstacles, Comb& comber);
private:
const Point dest_point; //!< Either the eventual startPoint or the eventual endPoint of this combing move
unsigned int dest_part_idx; //!< The index into Comb:partsView_inside of the part in which the \p dest_point is.
/*!
* 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 outside The outside boundary polygons
* \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
* \param comber[in] The combing calculator which has references to the offsets and boundaries to use in combing.
* \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(const Polygons& outside, const PolygonRef from, Point estimated_start, Point estimated_end, Comb& comber);
};
SliceDataStorage& storage; //!< The storage from 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.
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 offset_from_inside_to_outside; //!< The sum of the offsets for the inside and outside boundary Comb::offset_from_outlines and Comb::offset_from_outlines_outside
const int64_t max_crossing_dist2; //!< The maximal distance by which to cross the in_between area between inside and outside
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.
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();
/*!
* Get the BucketGrid mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
*/
BucketGrid2D<PolygonsPointIndex>& getOutsideLocToLine();
/*!
* Move the startPoint or endPoint inside when it should be inside
* \param is_inside[in] Whether the \p dest_point should be inside
* \param dest_point[in,out] The point to move
* \param start_inside_poly[out] The polygon in which the point has been moved
* \return Whether we have moved the point inside
*/
bool moveInside(bool is_inside, Point& dest_point, unsigned int& start_inside_poly);
public:
/*!
* Initializes the combing areas for every mesh in the layer (not support)
* \param storage Where the layer polygon data is stored
* \param layer_nr The number of the layer for which to generate the combing areas.
* \param comb_boundary_inside The comb boundary within which to comb within layer parts.
* \param offset_from_outlines The offset from the outline polygon, to create the combing boundary in case there is no second wall.
* \param travel_avoid_other_parts Whether to avoid other layer parts when traveling through air.
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
*/
Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
~Comb();
/*!
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
*
* \param startPoint Where to start moving from
* \param endPoint Where to move to
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
* \param startInside Whether we want to start inside the comb boundary
* \param endInside Whether we want to end up inside the comb boundary
* \param via_outside_makes_combing_fail When going through air is inavoidable, stop calculation early and return false.
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
* \return Whether combing has succeeded; otherwise a retraction is needed.
*/
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored, bool via_outside_makes_combing_fail, bool fail_on_unavoidable_obstacles);
};
}//namespace cura
#endif//PATH_PLANNING_COMB_H
-17
Ver Arquivo
@@ -1,17 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef PATH_PLANNING_COMB_PATH_H
#define PATH_PLANNING_COMB_PATH_H
#include "../utils/intpoint.h"
namespace cura
{
struct CombPath : public std::vector<Point> //!< A single path either inside or outise the parts
{
bool cross_boundary = false; //!< Whether the path crosses a boundary.
};
}//namespace cura
#endif//PATH_PLANNING_COMB_PATH_H
-17
Ver Arquivo
@@ -1,17 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef PATH_PLANNING_COMB_PATHS_H
#define PATH_PLANNING_COMB_PATHS_H
#include "CombPath.h"
namespace cura
{
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.
};
}//namespace cura
#endif//PATH_PLANNING_COMB_PATHS_H
-220
Ver Arquivo
@@ -1,220 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "LinePolygonsCrossings.h"
#include <algorithm>
#include "../utils/polygonUtils.h"
#include "../sliceDataStorage.h"
#include "../utils/SVG.h"
namespace cura {
bool LinePolygonsCrossings::calcScanlineCrossings(bool fail_on_unavoidable_obstacles)
{
min_crossing_idx = NO_INDEX;
max_crossing_idx = NO_INDEX;
for(unsigned int poly_idx = 0; poly_idx < boundary.size(); poly_idx++)
{
PolyCrossings minMax(poly_idx);
PolygonRef poly = boundary[poly_idx];
Point p0 = transformation_matrix.apply(poly[poly.size() - 1]);
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
Point p1 = transformation_matrix.apply(poly[point_idx]);
if ((p0.Y >= transformed_startPoint.Y && p1.Y <= transformed_startPoint.Y) || (p1.Y >= transformed_startPoint.Y && p0.Y <= transformed_startPoint.Y))
{ // if line segment crosses the line through the transformed start and end point (aka scanline)
if (p1.Y == p0.Y) //Line segment is parallel with the scanline. That means that both endpoints lie on the scanline, so they will have intersected with the adjacent line.
{
p0 = p1;
continue;
}
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y); // intersection point between line segment and the scanline
if (x >= transformed_startPoint.X && x <= transformed_endPoint.X)
{
if (!((p1.Y == transformed_startPoint.Y && p1.Y < p0.Y) || (p0.Y == transformed_startPoint.Y && p0.Y < p1.Y)))
{ // perform edge case only for line segments on and below the scanline, not for line segments on and above.
// \/ will be no crossings and /\ two, but most importantly | will be one crossing.
minMax.n_crossings++;
}
if(x < minMax.min.x) //For the leftmost intersection, move x left to stay outside of the border.
//Note: The actual distance from the intersection to the border is almost always less than dist_to_move_boundary_point_outside, since it only moves along the direction of the scanline.
{
minMax.min.x = x;
minMax.min.point_idx = point_idx;
}
if(x > minMax.max.x) //For the rightmost intersection, move x right to stay outside of the border.
{
minMax.max.x = x;
minMax.max.point_idx = point_idx;
}
}
}
p0 = p1;
}
if (fail_on_unavoidable_obstacles && minMax.n_crossings % 2 == 1)
{ // if start area and end area are not the same
return false;
}
else if (minMax.min.point_idx != NO_INDEX) // then always also max.point_idx != NO_INDEX
{ // if this polygon crossed the scanline
if (min_crossing_idx == NO_INDEX || minMax.min.x < crossings[min_crossing_idx].min.x) { min_crossing_idx = crossings.size(); }
if (max_crossing_idx == NO_INDEX || minMax.max.x > crossings[max_crossing_idx].max.x) { max_crossing_idx = crossings.size(); }
crossings.push_back(minMax);
}
}
return true;
}
bool LinePolygonsCrossings::lineSegmentCollidesWithBoundary()
{
Point diff = endPoint - startPoint;
transformation_matrix = PointMatrix(diff);
transformed_startPoint = transformation_matrix.apply(startPoint);
transformed_endPoint = transformation_matrix.apply(endPoint);
for(PolygonRef poly : boundary)
{
Point p0 = transformation_matrix.apply(poly.back());
for(Point p1_ : poly)
{
Point p1 = transformation_matrix.apply(p1_);
if ((p0.Y > transformed_startPoint.Y && p1.Y < transformed_startPoint.Y) || (p1.Y > transformed_startPoint.Y && p0.Y < transformed_startPoint.Y))
{
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
return true;
}
p0 = p1;
}
}
return false;
}
bool LinePolygonsCrossings::getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
{
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored) || !lineSegmentCollidesWithBoundary())
{
//We're not crossing any boundaries. So skip the comb generation.
combPath.push_back(startPoint);
combPath.push_back(endPoint);
return true;
}
bool success = calcScanlineCrossings(fail_on_unavoidable_obstacles);
if (!success)
{
return false;
}
CombPath basicPath;
getBasicCombingPath(basicPath);
optimizePath(basicPath, combPath);
// combPath = basicPath; // uncomment to disable comb path optimization
return true;
}
void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
{
for (PolyCrossings* crossing = getNextPolygonAlongScanline(transformed_startPoint.X)
; crossing != nullptr
; crossing = getNextPolygonAlongScanline(crossing->max.x))
{
getBasicCombingPath(*crossing, combPath);
}
combPath.push_back(endPoint);
}
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
{
PolygonRef poly = boundary[polyCrossings.poly_idx];
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - 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
for(unsigned int point_idx = polyCrossings.min.point_idx
; point_idx != polyCrossings.max.point_idx
; point_idx = (point_idx < poly.size() - 1) ? (point_idx + 1) : (0))
{
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
}
}
else
{ // follow the path in the opposite direction of the winding order of the boundary polygon
unsigned int min_idx = (polyCrossings.min.point_idx == 0)? poly.size() - 1: polyCrossings.min.point_idx - 1;
unsigned int max_idx = (polyCrossings.max.point_idx == 0)? poly.size() - 1: polyCrossings.max.point_idx - 1;
for(unsigned int point_idx = min_idx; point_idx != max_idx; point_idx = (point_idx > 0) ? (point_idx - 1) : (poly.size() - 1))
{
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
}
}
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
}
LinePolygonsCrossings::PolyCrossings* LinePolygonsCrossings::getNextPolygonAlongScanline(int64_t x)
{
PolyCrossings* ret = nullptr;
for(PolyCrossings& crossing : crossings)
{
if (crossing.min.x > x && (ret == nullptr || crossing.min.x < ret->min.x) )
{
ret = &crossing;
}
}
return ret;
}
bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimized_comb_path)
{
optimized_comb_path.push_back(startPoint);
for(unsigned int point_idx = 1; point_idx<comb_path.size(); point_idx++)
{
if(comb_path[point_idx] == comb_path[point_idx - 1]) //Two points are the same. Skip the second.
{
continue;
}
Point& current_point = optimized_comb_path.back();
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
{
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx - 1]))
{
comb_path.cross_boundary = true;
}
optimized_comb_path.push_back(comb_path[point_idx - 1]);
}
else
{
// : dont add the newest point
// TODO: add the below extra optimization? (+/- 7% extra computation time, +/- 2% faster print for Dual_extrusion_support_generation.stl)
while (optimized_comb_path.size() > 1)
{
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
{
break;
}
else
{
optimized_comb_path.pop_back();
}
}
}
}
optimized_comb_path.push_back(comb_path.back());
return true;
}
}//namespace cura
-29
Ver Arquivo
@@ -1,29 +0,0 @@
/** 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
-29
Ver Arquivo
@@ -1,29 +0,0 @@
/** 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
-52
Ver Arquivo
@@ -1,52 +0,0 @@
/** 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
-54
Ver Arquivo
@@ -1,54 +0,0 @@
/** 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
+3 -5
Ver Arquivo
@@ -1,6 +1,4 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include <clipper/clipper.hpp>
#include "raft.h"
#include "support.h"
@@ -10,15 +8,15 @@ void generateRaft(SliceDataStorage& storage, int distance)
{
if (storage.draft_protection_shield.size() > 0)
{
storage.raftOutline = storage.raftOutline.unionPolygons(storage.draft_protection_shield.offset(distance, ClipperLib::jtRound));
storage.raftOutline = storage.raftOutline.unionPolygons(storage.draft_protection_shield.offset(distance));
}
else if (storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0)
{
storage.raftOutline = storage.raftOutline.unionPolygons(storage.oozeShield[0].offset(distance, ClipperLib::jtRound));
storage.raftOutline = storage.raftOutline.unionPolygons(storage.oozeShield[0].offset(distance));
}
else
{
storage.raftOutline = storage.getLayerOutlines(0, true).offset(distance, ClipperLib::jtRound);
storage.raftOutline = storage.getLayerOutlines(0, true).offset(distance);
}
}
+349
Ver Arquivo
@@ -0,0 +1,349 @@
#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, nullptr);
return &children.back();
}
SettingConfig::SettingConfig(std::string key, std::string label, SettingContainer* parent)
: SettingContainer(key, label)
, parent(parent)
{
// std::cerr << key << std::endl; // debug output to show all frontend registered settings...
}
void SettingContainer::debugOutputAllSettings()
{
std::cerr << "CATEGORY: " << key << std::endl;
for (SettingConfig& child : children)
{
child.debugOutputAllSettings();
}
}
bool SettingRegistry::settingExists(std::string key) const
{
return settings.find(key) != settings.end();
}
SettingConfig* SettingRegistry::getSettingConfig(std::string key)
{
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;
}
SettingRegistry::SettingRegistry()
{
}
bool SettingRegistry::settingsLoaded()
{
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"))
{
categories.emplace_back("machine_extruder_trains", "Extruder Trains Settings Objects");
SettingContainer* category_trains = &categories.back();
const rapidjson::Value& trains = json_document["machine_extruder_trains"];
if (trains.IsArray())
{
if (trains.Size() > 0 && trains[0].IsObject())
{
unsigned int idx = 0;
for (auto it = trains.Begin(); it != trains.End(); ++it)
{
SettingConfig* child = category_trains->addChild(std::to_string(idx), std::to_string(idx));
for (rapidjson::Value::ConstMemberIterator setting_iterator = it->MemberBegin(); setting_iterator != it->MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(child, setting_iterator, warn_duplicates, false);
}
idx++;
}
}
}
else
{
logError("Error: JSON machine_extruder_trains is not an array!\n");
}
}
if (json_document.HasMember("machine_settings"))
{
categories.emplace_back("machine_settings", "Machine Settings");
SettingContainer* category_machine_settings = &categories.back();
const rapidjson::Value& json_object_container = json_document["machine_settings"];
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(category_machine_settings, setting_iterator, warn_duplicates);
}
}
if (json_document.HasMember("categories"))
{
for (rapidjson::Value::ConstMemberIterator category_iterator = json_document["categories"].MemberBegin(); category_iterator != json_document["categories"].MemberEnd(); ++category_iterator)
{
if (!category_iterator->value.IsObject())
{
continue;
}
if (!category_iterator->value.HasMember("settings") || !category_iterator->value["settings"].IsObject())
{
continue;
}
std::string cat_name = category_iterator->name.GetString();
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
SettingContainer* category = &*category_found;
const rapidjson::Value& json_object_container = category_iterator->value["settings"];
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(category, setting_iterator, warn_duplicates);
}
}
else
{
if (!category_iterator->value.HasMember("label") || !category_iterator->value["label"].IsString())
{
continue;
}
categories.emplace_back(cat_name, category_iterator->value["label"].GetString());
SettingContainer* category = &categories.back();
const rapidjson::Value& json_object_container = category_iterator->value["settings"];
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(category, setting_iterator, warn_duplicates);
}
}
}
}
if (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.", setting.c_str());
continue;
}
_loadSettingValues(conf, override_iterator, false);
}
}
return 0;
}
void SettingRegistry::_addSettingToContainer(SettingContainer* parent, rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings)
{
const rapidjson::Value& data = json_object_it->value;
if (data.HasMember("type") && data["type"].IsString() &&
(data["type"].GetString() == std::string("polygon") || data["type"].GetString() == std::string("polygons")))
{
logWarning("Loading polygon setting %s not implemented...\n", json_object_it->name.GetString());
/// When this setting has children, add those children to the parent setting.
if (data.HasMember("children") && data["children"].IsObject())
{
const rapidjson::Value& json_object_container = data["children"];
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(parent, setting_iterator, warn_duplicates, add_to_settings);
}
}
return;
}
std::string label;
if (!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->addChild(json_object_it->name.GetString(), label);
_loadSettingValues(config, json_object_it, warn_duplicates, add_to_settings);
}
void SettingRegistry::_loadSettingValues(SettingConfig* config, 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
{
logError("Unrecognized data type in JSON: %s has type %s\n", json_object_it->name.GetString(), toString(dflt.GetType()).c_str());
}
}
if (data.HasMember("unit") && data["unit"].IsString())
{
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
+225
Ver Arquivo
@@ -0,0 +1,225 @@
#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;
/*!
* Setting category.
* Filled from the fdmprinter.json file. Contains one or more children settings.
*/
class SettingContainer
{
friend class SettingConfig;
private:
std::string key;
std::string label;
std::list<SettingConfig> children;
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;
}
void debugOutputAllSettings();
};
/*!
* 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;
SettingContainer* parent;
public:
SettingConfig(std::string key, std::string label, SettingContainer* parent);
/*!
* Get the SettingConfig::children.
*
* This is used to get the extruder trains; see Settingsbase::setExtruderTrainDefaults
*
* \return SettingConfig::children
*/
const std::list<SettingConfig>& getChildren() const { return children; }
std::string getKey() const
{
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()
{
std::cerr << key << std::endl;
for (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.
*/
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);
/*!
* Return the first category with the given key as name, or a null pointer.
*
* \param key the key as it is in the JSON file
* \return The first category in the list having the \p key
*/
SettingContainer* getCategory(std::string key);
bool settingsLoaded();
/*!
* 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()
{
for (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, rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
void _loadSettingValues(SettingConfig* config, rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
};
}//namespace cura
#endif//SETTING_REGISTRY_H
+119 -88
Ver Arquivo
@@ -1,14 +1,11 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include <cctype>
#include <fstream>
#include <stdio.h>
#include <sstream> // ostringstream
#include <regex> // regex parsing for temp flow graph
#include <string> // stod (string to double)
#include "../utils/logoutput.h"
#include "utils/logoutput.h"
#include "settings.h"
#include "SettingRegistry.h"
#include "settingRegistry.h"
namespace cura
{
@@ -31,8 +28,6 @@ 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";
@@ -64,39 +59,40 @@ 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))
{
_setSetting(key, value);
setting_values[key] = value;
}
else
{
cura::logError("Warning: setting an unregistered setting %s to %s\n", key.c_str(), value.c_str());
_setSetting(key, value); // Handy when programmers are in the process of introducing a new setting
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
}
}
std::string SettingsBase::getSettingString(std::string key) const
std::string SettingsBase::getSettingString(std::string key)
{
if (setting_values.find(key) != setting_values.end())
{
return setting_values.at(key);
return setting_values[key];
}
if (parent)
{
return parent->getSettingString(key);
}
const_cast<SettingsBase&>(*this).setting_values[key] = "";
cura::logError("Unregistered setting %s\n", key.c_str());
return "";
if (SettingRegistry::getInstance()->settingExists(key))
{
setting_values[key] = SettingRegistry::getInstance()->getSettingConfig(key)->getDefaultValue();
}
else
{
setting_values[key] = "";
cura::logError("Unregistered setting %s\n", key.c_str());
}
return setting_values[key];
}
void SettingsMessenger::setSetting(std::string key, std::string value)
@@ -104,41 +100,64 @@ void SettingsMessenger::setSetting(std::string key, std::string value)
parent->setSetting(key, value);
}
std::string SettingsMessenger::getSettingString(std::string key) const
std::string SettingsMessenger::getSettingString(std::string key)
{
return parent->getSettingString(key);
}
int SettingsBaseVirtual::getSettingAsIndex(std::string key) const
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)
{
std::string value = getSettingString(key);
return atoi(value.c_str());
}
int SettingsBaseVirtual::getSettingAsCount(std::string key) const
int SettingsBaseVirtual::getSettingAsCount(std::string key)
{
std::string value = getSettingString(key);
return atoi(value.c_str());
}
double SettingsBaseVirtual::getSettingInMillimeters(std::string key) const
int SettingsBaseVirtual::getSettingInMicrons(std::string key)
{
std::string value = getSettingString(key);
return atof(value.c_str());
return atof(value.c_str()) * 1000.0;
}
int SettingsBaseVirtual::getSettingInMicrons(std::string key) const
{
return getSettingInMillimeters(key) * 1000.0;
}
double SettingsBaseVirtual::getSettingInAngleRadians(std::string key) const
double SettingsBaseVirtual::getSettingInAngleRadians(std::string key)
{
std::string value = getSettingString(key);
return atof(value.c_str()) / 180.0 * M_PI;
}
bool SettingsBaseVirtual::getSettingBoolean(std::string key) const
bool SettingsBaseVirtual::getSettingBoolean(std::string key)
{
std::string value = getSettingString(key);
if (value == "on")
@@ -151,71 +170,99 @@ bool SettingsBaseVirtual::getSettingBoolean(std::string key) const
return num != 0;
}
double SettingsBaseVirtual::getSettingInDegreeCelsius(std::string key) const
double SettingsBaseVirtual::getSettingInDegreeCelsius(std::string key)
{
std::string value = getSettingString(key);
return atof(value.c_str());
}
double SettingsBaseVirtual::getSettingInMillimetersPerSecond(std::string key) const
double SettingsBaseVirtual::getSettingInMillimetersPerSecond(std::string key)
{
std::string value = getSettingString(key);
return std::max(1.0, atof(value.c_str()));
}
double SettingsBaseVirtual::getSettingInCubicMillimeters(std::string key) const
double SettingsBaseVirtual::getSettingInCubicMillimeters(std::string key)
{
std::string value = getSettingString(key);
return std::max(0.0, atof(value.c_str()));
}
double SettingsBaseVirtual::getSettingInPercentage(std::string key) const
double SettingsBaseVirtual::getSettingInPercentage(std::string key)
{
std::string value = getSettingString(key);
return std::max(0.0, atof(value.c_str()));
}
double SettingsBaseVirtual::getSettingInSeconds(std::string key) const
double SettingsBaseVirtual::getSettingInSeconds(std::string key)
{
std::string value = getSettingString(key);
return std::max(0.0, atof(value.c_str()));
}
FlowTempGraph SettingsBaseVirtual::getSettingAsFlowTempGraph(std::string key) const
FlowTempGraph SettingsBaseVirtual::getSettingAsFlowTempGraph(std::string key)
{
FlowTempGraph ret;
std::string value_string = getSettingString(key);
if (value_string.empty())
const char* c_str = getSettingString(key).c_str();
char const* char_p = c_str;
while (*char_p != '[')
{
return ret; //Empty at this point.
if (*char_p == '\0') //We've reached the end of string without encountering the first opening bracket.
{
return ret; //Empty at this point.
}
char_p++;
}
std::regex regex("(\\[([^,\\[]*),([^,\\]]*)\\])");
// default constructor = end-of-sequence:
std::regex_token_iterator<std::string::iterator> rend;
int submatches[] = { 1, 2, 3 }; // match whole pair, first number and second number of a pair
std::regex_token_iterator<std::string::iterator> a(value_string.begin(), value_string.end(), regex, submatches);
while (a != rend)
char_p++; // skip the '['
for (; *char_p != '\0'; char_p++)
{
a++; // match the whole pair
if (a == rend)
while (*char_p != '[')
{
if (*char_p == '\0') //We've reached the end of string without finding the next opening bracket.
{
return ret; //Don't continue parsing this item then. Just stop and return.
}
char_p++;
}
char_p++; // skip the '['
char* end;
double first = strtod(char_p, &end); //If not a valid number, this becomes zero.
char_p = end;
while (*char_p != ',')
{
if (*char_p == '\0') //We've reached the end of string without finding the comma.
{
return ret; //This entry is incomplete.
}
char_p++;
}
char_p++; // skip the ','
double second = strtod(char_p, &end); //If not a valid number, this becomes zero.
ret.data.emplace_back(first, second);
char_p = end;
while (*char_p != ']')
{
if (*char_p == '\0') //We've reached the end of string without finding the closing bracket.
{
return ret; //This entry is probably complete and has been added, but stop searching.
}
char_p++;
}
char_p++; // skip the ']'
if (*char_p == ']' || *char_p == '\0')
{
break;
}
double first = std::stod(*a++);
double second = std::stod(*a++);
ret.data.emplace_back(first, second);
}
return ret;
}
EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key) const
EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key)
{
std::string value = getSettingString(key);
if (value == "Griffin")
return EGCodeFlavor::GRIFFIN;
if (value == "RepRap")
return EGCodeFlavor::REPRAP;
else if (value == "UltiGCode")
return EGCodeFlavor::ULTIGCODE;
else if (value == "Makerbot")
@@ -229,7 +276,7 @@ EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key) const
return EGCodeFlavor::REPRAP;
}
EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key) const
EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key)
{
std::string value = getSettingString(key);
if (value == "lines")
@@ -245,7 +292,7 @@ EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key) const
return EFillMethod::NONE;
}
EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string key) const
EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string key)
{
std::string value = getSettingString(key);
if (value == "brim")
@@ -255,7 +302,7 @@ EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string
return EPlatformAdhesion::SKIRT;
}
ESupportType SettingsBaseVirtual::getSettingAsSupportType(std::string key) const
ESupportType SettingsBaseVirtual::getSettingAsSupportType(std::string key)
{
std::string value = getSettingString(key);
if (value == "everywhere")
@@ -265,7 +312,7 @@ ESupportType SettingsBaseVirtual::getSettingAsSupportType(std::string key) const
return ESupportType::NONE;
}
EZSeamType SettingsBaseVirtual::getSettingAsZSeamType(std::string key) const
EZSeamType SettingsBaseVirtual::getSettingAsZSeamType(std::string key)
{
std::string value = getSettingString(key);
if (value == "random")
@@ -277,7 +324,7 @@ EZSeamType SettingsBaseVirtual::getSettingAsZSeamType(std::string key) const
return EZSeamType::SHORTEST;
}
ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key)
{
std::string value = getSettingString(key);
if (value == "normal")
@@ -289,38 +336,22 @@ ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
return ESurfaceMode::NORMAL;
}
CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key)
FillPerimeterGapMode SettingsBaseVirtual::getSettingAsFillPerimeterGapMode(std::string key)
{
std::string value = getSettingString(key);
if (value == "off")
if (value == "nowhere")
{
return CombingMode::OFF;
return FillPerimeterGapMode::NOWHERE;
}
if (value == "all")
if (value == "everywhere")
{
return CombingMode::ALL;
return FillPerimeterGapMode::EVERYWHERE;
}
if (value == "noskin")
if (value == "skin")
{
return CombingMode::NO_SKIN;
return FillPerimeterGapMode::SKIN;
}
return CombingMode::ALL;
return FillPerimeterGapMode::NOWHERE;
}
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
+46 -68
Ver Arquivo
@@ -1,15 +1,14 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef SETTINGS_SETTINGS_H
#define SETTINGS_SETTINGS_H
#ifndef SETTINGS_H
#define 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
{
@@ -76,16 +75,6 @@ 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,
};
/*!
@@ -142,17 +131,11 @@ enum class ESurfaceMode
BOTH
};
enum class CombingMode
enum class FillPerimeterGapMode
{
OFF,
ALL,
NO_SKIN
};
enum class SupportDistPriority
{
XY_OVERRIDES_Z,
Z_OVERRIDES_XY
NOWHERE,
EVERYWHERE,
SKIN
};
#define MAX_EXTRUDERS 16
@@ -172,7 +155,7 @@ class SettingsBaseVirtual
protected:
SettingsBaseVirtual* parent;
public:
virtual std::string getSettingString(std::string key) const = 0;
virtual std::string getSettingString(std::string key) = 0;
virtual void setSetting(std::string key, std::string value) = 0;
@@ -184,32 +167,31 @@ public:
void setParent(SettingsBaseVirtual* parent) { this->parent = parent; }
SettingsBaseVirtual* getParent() { return parent; }
int getSettingAsIndex(std::string key) const;
int getSettingAsCount(std::string key) const;
int getSettingAsIndex(std::string key);
int getSettingAsCount(std::string key);
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;
double getSettingInMillimetersPerSecond(std::string key) const;
double getSettingInCubicMillimeters(std::string key) const;
double getSettingInPercentage(std::string key) const;
double getSettingInSeconds(std::string key) const;
double getSettingInAngleRadians(std::string key);
int getSettingInMicrons(std::string key);
bool getSettingBoolean(std::string key);
double getSettingInDegreeCelsius(std::string key);
double getSettingInMillimetersPerSecond(std::string key);
double getSettingInCubicMillimeters(std::string key);
double getSettingInPercentage(std::string key);
double getSettingInSeconds(std::string key);
FlowTempGraph getSettingAsFlowTempGraph(std::string key) const;
FlowTempGraph getSettingAsFlowTempGraph(std::string key);
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;
CombingMode getSettingAsCombingMode(std::string key);
SupportDistPriority getSettingAsSupportDistPriority(std::string key);
std::vector<std::pair<double, double>> getSettingAsPointVector(std::string key);
EGCodeFlavor getSettingAsGCodeFlavor(std::string key);
EFillMethod getSettingAsFillMethod(std::string key);
EPlatformAdhesion getSettingAsPlatformAdhesion(std::string key);
ESupportType getSettingAsSupportType(std::string key);
EZSeamType getSettingAsZSeamType(std::string key);
ESurfaceMode getSettingAsSurfaceMode(std::string key);
FillPerimeterGapMode getSettingAsFillPerimeterGapMode(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.
@@ -219,22 +201,26 @@ class SettingRegistry;
*/
class SettingsBase : public SettingsBaseVirtual
{
friend class SettingRegistry;
private:
std::unordered_map<std::string, std::string> setting_values;
public:
SettingsBase(); //!< SettingsBase without a parent settings object
SettingsBase(SettingsBaseVirtual* parent); //!< construct a SettingsBase with a parent settings object
/*!
* Set a setting to a value.
* \param key the setting
* \param value the value
*/
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 getAllLocalSettingsString() const
/*!
* Retrieve the defaults for each extruder train from the machine_extruder_trains settings
* and set the general settings to those defaults if they haven't been set yet.
*
* Only sets those settings which haven't already been set on that level - not looking at its parent (FffProcessor, meshgroup) or children (meshes).
*
* \param extruder_nr The index of which extruder train in machine_extruder_trains to get the settings from
*/
void setExtruderTrainDefaults(unsigned int extruder_nr);
void setSetting(std::string key, std::string value);
std::string getSettingString(std::string key); //!< Get a setting from this SettingsBase (or any ancestral SettingsBase)
std::string getAllLocalSettingsString()
{
std::stringstream sstream;
for (auto pair : setting_values)
@@ -247,18 +233,11 @@ public:
return sstream.str();
}
void debugOutputAllLocalSettings() const
void debugOutputAllLocalSettings()
{
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);
};
/*!
@@ -272,10 +251,9 @@ public:
SettingsMessenger(SettingsBaseVirtual* parent); //!< construct a SettingsMessenger with a parent settings object
void setSetting(std::string key, std::string value); //!< Set a setting of the parent SettingsBase to a given value
std::string getSettingString(std::string key) const; //!< Get a setting from the parent SettingsBase (or any further ancestral SettingsBase)
std::string getSettingString(std::string key); //!< Get a setting from the parent SettingsBase (or any further ancestral SettingsBase)
};
}//namespace cura
#endif//SETTINGS_SETTINGS_H
#endif//SETTINGS_H
-14
Ver Arquivo
@@ -1,14 +0,0 @@
/** 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
-76
Ver Arquivo
@@ -1,76 +0,0 @@
/** 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
-47
Ver Arquivo
@@ -1,47 +0,0 @@
/** 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
-83
Ver Arquivo
@@ -1,83 +0,0 @@
/** 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
-397
Ver Arquivo
@@ -1,397 +0,0 @@
/** 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[filename.size()];
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
setting_key_to_config[name] = nullptr; // add the category name to the mapping, but don't instantiate a setting config for it.
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);
}
else
{
setting_key_to_config[name] = nullptr; // add the setting name to the mapping, but don't instantiate a setting config for it.
}
}
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
-191
Ver Arquivo
@@ -1,191 +0,0 @@
/** 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
-238
Ver Arquivo
@@ -1,238 +0,0 @@
/** 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
+68 -41
Ver Arquivo
@@ -8,21 +8,21 @@ namespace cura
{
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)
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)
{
generateSkinAreas(layerNr, mesh, innermost_wall_extrusion_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
generateSkinAreas(layerNr, storage, innermost_wall_extrusion_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
SliceLayer* layer = &mesh.layers[layerNr];
SliceLayer* layer = &storage.layers[layerNr];
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
{
SliceLayerPart* part = &layer->parts[partNr];
generateSkinInsets(part, extrusionWidth, insetCount);
generateSkinInsets(part, extrusionWidth, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
}
}
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)
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)
{
SliceLayer& layer = mesh.layers[layer_nr];
SliceLayer& layer = storage.layers[layer_nr];
if (downSkinCount == 0 && upSkinCount == 0)
{
@@ -42,16 +42,13 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
Polygons downskin = (downSkinCount == 0)? Polygons() : upskin;
if (upSkinCount == 0) upskin = Polygons();
auto getInsidePolygons = [&part, wall_line_count](SliceLayer& layer2)
auto getInsidePolygons = [&part](SliceLayer& layer2)
{
Polygons result;
for(SliceLayerPart& part2 : layer2.parts)
{
if (part.boundaryBox.hit(part2.boundaryBox))
{
unsigned int wall_idx = std::max(0, std::min(wall_line_count, (int) part2.insets.size()) - 1);
result.add(part2.insets[wall_idx]);
}
result.add(part2.insets.back());
}
return result;
};
@@ -60,32 +57,32 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
{
if (static_cast<int>(layer_nr - downSkinCount) >= 0)
{
downskin = downskin.difference(getInsidePolygons(mesh.layers[layer_nr - downSkinCount])); // skin overlaps with the walls
downskin = downskin.difference(getInsidePolygons(storage.layers[layer_nr - downSkinCount])); // skin overlaps with the walls
}
if (static_cast<int>(layer_nr + upSkinCount) < static_cast<int>(mesh.layers.size()))
if (static_cast<int>(layer_nr + upSkinCount) < static_cast<int>(storage.layers.size()))
{
upskin = upskin.difference(getInsidePolygons(mesh.layers[layer_nr + upSkinCount])); // skin overlaps with the walls
upskin = upskin.difference(getInsidePolygons(storage.layers[layer_nr + upSkinCount])); // skin overlaps with the walls
}
}
else
{
if (layer_nr >= downSkinCount && downSkinCount > 0)
if (layer_nr > 0 && downSkinCount > 0)
{
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++)
Polygons not_air = getInsidePolygons(storage.layers[layer_nr - 1]);
for (int downskin_layer_nr = std::max(0, layer_nr - downSkinCount); downskin_layer_nr < layer_nr - 1; downskin_layer_nr++)
{
not_air = not_air.intersection(getInsidePolygons(mesh.layers[downskin_layer_nr]));
not_air = not_air.intersection(getInsidePolygons(storage.layers[downskin_layer_nr]));
}
downskin = downskin.difference(not_air); // skin overlaps with the walls
}
if (layer_nr < static_cast<int>(mesh.layers.size()) - 1 && upSkinCount > 0)
if (layer_nr < static_cast<int>(storage.layers.size()) - 1 && upSkinCount > 0)
{
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++)
Polygons not_air = getInsidePolygons(storage.layers[layer_nr + 1]);
for (int upskin_layer_nr = layer_nr + 2; upskin_layer_nr < std::min(static_cast<int>(storage.layers.size()) - 1, layer_nr + upSkinCount); upskin_layer_nr++)
{
not_air = not_air.intersection(getInsidePolygons(mesh.layers[upskin_layer_nr]));
not_air = not_air.intersection(getInsidePolygons(storage.layers[upskin_layer_nr]));
}
upskin = upskin.difference(not_air); // skin overlaps with the walls
}
@@ -104,7 +101,7 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
}
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount)
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
{
if (insetCount == 0)
{
@@ -118,10 +115,12 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
skin_part.insets.push_back(Polygons());
if (i == 0)
{
skin_part.insets[0] = skin_part.outline.offset(- extrusionWidth/2);
PolygonUtils::offsetSafe(skin_part.outline, - extrusionWidth/2, extrusionWidth, skin_part.insets[0], avoidOverlappingPerimeters_0);
Polygons in_between = skin_part.outline.difference(skin_part.insets[0].offset(extrusionWidth/2));
skin_part.perimeterGaps.add(in_between);
} else
{
skin_part.insets[i] = skin_part.insets[i - 1].offset(-extrusionWidth);
PolygonUtils::offsetExtrusionWidth(skin_part.insets[i-1], true, extrusionWidth, skin_part.insets[i], &skin_part.perimeterGaps, avoidOverlappingPerimeters);
}
// optimize polygons: remove unnnecesary verts
@@ -135,15 +134,14 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
}
}
void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
{
SliceLayer& layer = mesh.layers[layerNr];
SliceLayer& layer = storage.layers[layerNr];
for(SliceLayerPart& part : layer.parts)
{
if (int(part.insets.size()) < wall_line_count)
{
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);
@@ -160,19 +158,17 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extr
}
infill.removeSmallAreas(MIN_AREA_SIZE);
part.infill_area = infill.offset(infill_skin_overlap);
part.infill_area_per_combine.push_back(part.infill_area);
part.infill_area.push_back(infill.offset(infill_skin_overlap));
}
}
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount)
void combineInfillLayers(SliceMeshStorage& storage,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 (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.
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.
{
return;
}
@@ -180,13 +176,13 @@ void combineInfillLayers(SliceMeshStorage& mesh, 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 = mesh.getSettingAsCount("bottom_layers") + amount - 1;
size_t min_layer = storage.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 = mesh.layers.size() - 1 - mesh.getSettingAsCount("top_layers");
size_t max_layer = storage.layers.size() - 1 - storage.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 = &mesh.layers[layer_idx];
SliceLayer* layer = &storage.layers[layer_idx];
for(unsigned int n = 1;n < amount;n++)
{
@@ -195,7 +191,7 @@ void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount)
break;
}
SliceLayer* layer2 = &mesh.layers[layer_idx - n];
SliceLayer* layer2 = &storage.layers[layer_idx - n];
for(SliceLayerPart& part : layer->parts)
{
Polygons result;
@@ -203,18 +199,49 @@ void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount)
{
if(part.boundaryBox.hit(part2.boundaryBox))
{
Polygons intersection = part.infill_area_per_combine[n - 1].intersection(part2.infill_area_per_combine[0]).offset(-200).offset(200);
Polygons intersection = part.infill_area[n - 1].intersection(part2.infill_area[0]).offset(-200).offset(200);
result.add(intersection);
part.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[n - 1] = part.infill_area[n - 1].difference(intersection);
part2.infill_area[0] = part2.infill_area[0].difference(intersection);
}
}
part.infill_area_per_combine.push_back(result);
part.infill_area.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
+24 -11
Ver Arquivo
@@ -6,11 +6,23 @@
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 mesh The storage where the layer outline information (input) is stored and where the skin insets and fill areas (output) are stored.
* \param storage 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
@@ -18,21 +30,23 @@ namespace cura
* \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& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic);
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);
/*!
* Generate the skin areas (outlines)
*
* \param layerNr The index of the layer for which to generate the skins.
* \param mesh The storage where the layer outline information (input) is stored and where the skin outline (output) is stored.
* \param storage 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& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
/*!
* Generate the skin insets.
@@ -41,24 +55,23 @@ void generateSkinAreas(int layerNr, SliceMeshStorage& mesh, int extrusionWidth,
* \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);
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
/*!
* Generate Infill by offsetting from the last wall.
*
* The walls should already be generated.
*
* After this function has been called on a layer of a mesh, each SliceLayerPart of that layer should have an infill_area consisting of exactly one Polygons : the normal uncombined infill area.
*
* \param layerNr The index of the layer for which to generate the infill
* \param 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& mesh, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
/*!
* \brief Combines the infill of multiple layers for a specified mesh.
@@ -67,10 +80,10 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extr
* multiplied such that the infill should fill up again to the full height of
* all combined layers.
*
* \param mesh The mesh to combine the infill layers of.
* \param storage The mesh to combine the infill layers of.
* \param amount The number of layers to combine.
*/
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount);
void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount);
}//namespace cura
+3 -3
Ver Arquivo
@@ -12,10 +12,10 @@ void generateSkirt(SliceDataStorage& storage, int distance, int count, int minLe
if (count == 0) return;
bool externalOnly = (distance > 0); // whether to include holes or not
int primary_extruder = storage.getSettingAsIndex("adhesion_extruder_nr");
int primary_extruder = 0; // TODO allow for other extruder to be primary
int primary_extrusion_width = storage.meshgroup->getExtruderTrain(primary_extruder)->getSettingInMicrons("skirt_line_width");
Polygons& skirt_primary_extruder = storage.skirt[primary_extruder];
bool get_convex_hull = count == 1 && distance > 0;
+24 -85
Ver Arquivo
@@ -5,47 +5,47 @@
namespace cura
{
Polygons SliceLayer::getOutlines(bool external_polys_only) const
Polygons SliceLayer::getOutlines(bool external_polys_only)
{
Polygons ret;
getOutlines(ret, external_polys_only);
return ret;
}
void SliceLayer::getOutlines(Polygons& result, bool external_polys_only) const
void SliceLayer::getOutlines(Polygons& result, bool external_polys_only)
{
for (const SliceLayerPart& part : parts)
for (SliceLayerPart& part : parts)
{
if (external_polys_only)
{
result.add(const_cast<SliceLayerPart&>(part).outline.outerPolygon()); // TODO: make a const version of outerPolygon()
result.add(part.outline.outerPolygon());
}
else
{
result.add(part.print_outline);
result.add(part.outline);
}
}
}
Polygons SliceLayer::getSecondOrInnermostWalls() const
Polygons SliceLayer::getSecondOrInnermostWalls()
{
Polygons ret;
getSecondOrInnermostWalls(ret);
return ret;
}
void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls) const
void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls)
{
for (const SliceLayerPart& part : parts)
for (SliceLayerPart& part : parts)
{
// we want the 2nd inner walls
if (part.insets.size() >= 2) {
layer_walls.add(const_cast<SliceLayerPart&>(part).insets[1]); // TODO const cast!
layer_walls.add(part.insets[1]);
continue;
}
// but we'll also take the inner wall if the 2nd doesn't exist
if (part.insets.size() == 1) {
layer_walls.add(const_cast<SliceLayerPart&>(part).insets[0]); // TODO const cast!
layer_walls.add(part.insets[0]);
continue;
}
// offset_from_outlines was so large that it completely destroyed our isle,
@@ -56,46 +56,21 @@ void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls) const
}
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++)
{
travel_config_per_extruder.emplace_back(PrintFeatureType::MoveCombing);
}
return ret;
}
std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtConfigs()
{
std::vector<GCodePathConfig> ret;
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
{
skirt_config.emplace_back(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()),
extruder_switch_retraction_config_per_extruder(initializeRetractionConfigs()),
travel_config_per_extruder(initializeTravelConfigs()),
travel_config(&retraction_config, PrintFeatureType::MoveCombing),
skirt_config(initializeSkirtConfigs()),
raft_base_config(PrintFeatureType::Support),
raft_interface_config(PrintFeatureType::Support),
raft_surface_config(PrintFeatureType::Support),
support_config(PrintFeatureType::Support),
support_roof_config(PrintFeatureType::Skin),
raft_base_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
raft_interface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
raft_surface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
support_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_extruder_nr")], PrintFeatureType::Support),
support_roof_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_roof_extruder_nr")], PrintFeatureType::Skin),
max_object_height_second_to_last_extruder(-1)
{
}
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) const
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only)
{
if (layer_nr < 0)
{ // when processing raft
@@ -124,15 +99,11 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
else
{
Polygons total;
for (const SliceMeshStorage& mesh : meshes)
for (SliceMeshStorage& mesh : meshes)
{
if (mesh.getSettingBoolean("infill_mesh"))
{
continue;
}
const SliceLayer& layer = mesh.layers[layer_nr];
SliceLayer& layer = mesh.layers[layer_nr];
layer.getOutlines(total, external_polys_only);
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make all getSetting functions const??
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
}
@@ -150,7 +121,7 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
}
}
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) const
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts)
{
if (layer_nr < 0)
{ // when processing raft
@@ -166,11 +137,11 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
else
{
Polygons total;
for (const SliceMeshStorage& mesh : meshes)
for (SliceMeshStorage& mesh : meshes)
{
const SliceLayer& layer = mesh.layers[layer_nr];
SliceLayer& layer = mesh.layers[layer_nr];
layer.getSecondOrInnermostWalls(total);
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make getSetting const? make settings.setting_values mapping mutable??
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
}
@@ -189,39 +160,7 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
}
std::vector< bool > SliceDataStorage::getExtrudersUsed()
{
std::vector<bool> ret;
ret.resize(meshgroup->getExtruderCount(), false);
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
{ // process brim/skirt
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
{
if (skirt[extr_nr].size() > 0)
{
ret[extr_nr] = true;
continue;
}
}
}
// TODO: ooze shield, draft shield ..?
// support
// support is presupposed to be present...
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
ret[getSettingAsIndex("support_roof_extruder_nr")] = true;
// all meshes are presupposed to actually have content
for (SliceMeshStorage& mesh : meshes)
{
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
}
return ret;
}
+33 -38
Ver Arquivo
@@ -5,12 +5,10 @@
#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"
#include "PrimeTower.h"
#include "GCodePathConfig.h"
namespace cura
{
@@ -24,6 +22,7 @@ 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)
@@ -35,11 +34,10 @@ 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.
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.
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.
};
/*!
@@ -60,7 +58,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) const;
Polygons getOutlines(bool external_polys_only = false);
/*!
* Get the all outlines of all layer parts in this layer.
@@ -69,20 +67,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) const;
void getOutlines(Polygons& result, bool external_polys_only = false);
/*!
* 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() const;
Polygons getSecondOrInnermostWalls();
/*!
* 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) const;
void getSecondOrInnermostWalls(Polygons& result);
};
/******************/
@@ -114,17 +112,18 @@ public:
int layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content
RetractionConfig retraction_config;
GCodePathConfig inset0_config;
GCodePathConfig insetX_config;
GCodePathConfig skin_config;
std::vector<GCodePathConfig> infill_config;
SliceMeshStorage(SettingsBaseVirtual* settings)
: SettingsMessenger(settings), layer_nr_max_filled_layer(0), inset0_config(PrintFeatureType::OuterWall), insetX_config(PrintFeatureType::InnerWall), skin_config(PrintFeatureType::Skin)
: SettingsMessenger(settings), layer_nr_max_filled_layer(0), inset0_config(&retraction_config, PrintFeatureType::OuterWall), insetX_config(&retraction_config, PrintFeatureType::InnerWall), skin_config(&retraction_config, PrintFeatureType::Skin)
{
infill_config.reserve(MAX_INFILL_COMBINE);
for(int n=0; n<MAX_INFILL_COMBINE; n++)
infill_config.emplace_back(PrintFeatureType::Infill);
infill_config.emplace_back(&retraction_config, PrintFeatureType::Infill);
}
};
@@ -136,11 +135,10 @@ public:
Point3 model_size, model_min, model_max;
std::vector<SliceMeshStorage> meshes;
std::vector<RetractionConfig> retraction_config_per_extruder; //!< Retraction config per extruder.
std::vector<RetractionConfig> extruder_switch_retraction_config_per_extruder; //!< Retraction config per extruder for when performing an extruder switch
std::vector<GCodePathConfig> travel_config_per_extruder; //!< The config used for travel moves (only speed is set!)
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> skirt_config; //!< config for skirt per extruder
std::vector<CoastingConfig> coasting_config; //!< coasting config per extruder
@@ -163,19 +161,23 @@ 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;
/*!
* 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();
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;
}
/*!
* \brief Creates a new slice data storage that stores the slice data of the
* specified mesh group.
@@ -199,7 +201,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) const;
Polygons getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only = false);
/*!
* 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.
@@ -209,14 +211,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) const;
/*!
* Get the extruders used.
*
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
*/
std::vector<bool> getExtrudersUsed();
Polygons getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts);
};
}//namespace cura
+232 -378
Ver Arquivo
@@ -1,459 +1,323 @@
/** 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)
void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive_stitching)
{
for(unsigned int start_segment_idx = 0; start_segment_idx < segments.size(); start_segment_idx++)
Polygons openPolygonList;
// connect line segments
for(unsigned int startSegment=0; startSegment < segmentList.size(); startSegment++)
{
if (!segments[start_segment_idx].addedToPolygon)
if (segmentList[startSegment].addedToPolygon)
continue;
Polygon poly;
poly.add(segmentList[startSegment].start);
unsigned int segmentIndex = startSegment;
bool canClose;
while(true)
{
makeBasicPolygonLoop(mesh, open_polylines, start_segment_idx);
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.
segments.clear();
}
void SlicerLayer::makeBasicPolygonLoop(const Mesh* mesh, Polygons& open_polylines, unsigned int start_segment_idx)
{
segmentList.clear();
Polygon poly;
poly.add(segments[start_segment_idx].start);
for (int segment_idx = start_segment_idx; segment_idx != -1; )
// 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++)
{
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())
if (openPolygonList[i].size() < 1) continue;
for(unsigned int j=0;j<openPolygonList.size();j++)
{
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 (openPolygonList[j].size() < 1) continue;
if (open_polyline_other.size() < 1) continue;
Point diff = open_polyline.back() - open_polyline_other[0];
Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0];
int64_t distSquared = vSize2(diff);
if (shorterThen(diff, largest_neglected_gap_second_phase))
if (distSquared < MM2INT(0.02) * MM2INT(0.02))
{
if (open_polyline_idx == open_polyline_other_idx)
if (i == j)
{
polygons.add(open_polyline);
open_polyline.clear();
polygonList.add(openPolygonList[i]);
openPolygonList[i].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();
}else{
for(unsigned int n=0; n<openPolygonList[j].size(); n++)
openPolygonList[i].add(openPolygonList[j][n]);
openPolygonList[j].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)
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::NORMAL)
{
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++)
//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)
{
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++)
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++)
{
PolygonRef polyline_2 = open_polylines[polyline_2_idx];
if (openPolygonList[j].size() < 1) continue;
if (polyline_2.size() < 1) continue;
Point diff = polyline_1.back() - polyline_2[0];
int64_t dist2 = vSize2(diff);
if (dist2 < best_dist2)
Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0];
int64_t distSquared = vSize2(diff);
if (distSquared < bestScore)
{
best_dist2 = dist2;
best_polyline_1_idx = polyline_1_idx;
best_polyline_2_idx = polyline_2_idx;
bestScore = distSquared;
bestA = i;
bestB = j;
reversed = false;
}
if (polyline_1_idx != polyline_2_idx)
if (i != j)
{
Point diff = polyline_1.back() - polyline_2.back();
int64_t dist2 = vSize2(diff);
if (dist2 < best_dist2)
Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][openPolygonList[j].size()-1];
int64_t distSquared = vSize2(diff);
if (distSquared < bestScore)
{
best_dist2 = dist2;
best_polyline_1_idx = polyline_1_idx;
best_polyline_2_idx = polyline_2_idx;
bestScore = distSquared;
bestA = i;
bestB = j;
reversed = true;
}
}
}
}
if (best_dist2 >= max_stitch1 * max_stitch1)
break; // this code is reached if there was nothing to stitch within the distance limits
if (bestScore >= MM2INT(10.0) * MM2INT(10.0))
break;
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 (bestA == bestB)
{
polygonList.add(openPolygonList[bestA]);
openPolygonList[bestA].clear();
}else{
if (reversed)
{
if (polyline_1.size() > polyline_2.size()) // decide which polygon to copy into the other
if (openPolygonList[bestA].polygonLength() > openPolygonList[bestB].polygonLength())
{
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();
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();
}
// 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();
}else{
for(unsigned int n=0; n<openPolygonList[bestB].size(); n++)
openPolygonList[bestA].add(openPolygonList[bestB][n]);
openPolygonList[bestB].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)
}
if (extensive_stitching)
{
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 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)
for(unsigned int polyline_1_idx = 0; polyline_1_idx < open_polylines.size(); polyline_1_idx++)
while(1)
{
PolygonRef polyline_1 = open_polylines[polyline_1_idx];
if (polyline_1.size() < 1) continue;
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++)
{
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;
if (openPolygonList[i].size() < 1) 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))
GapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]);
if (res.len > 0 && res.len < bestResult.len)
{
ret.intersectionPoint = q;
ret.polygonIdx = n;
ret.pointIdx = i;
return ret;
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;
}
}
}
p0 = p1;
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;
}
}
}
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)
for(unsigned int n=0; n<openPolygonList.size(); n++)
{
if (polyline.size() > 0)
openPolylines.add(polyline);
if (openPolygonList[n].size() > 0)
polygonList.add(openPolygonList[n]);
}
}
for (PolygonRef polyline : open_polylines)
for(unsigned int i=0;i<openPolygonList.size();i++)
{
if (polyline.size() > 0)
openPolylines.add(polyline);
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); // TODO: hardcoded value
auto it = std::remove_if(polygons.begin(), polygons.end(), [snapDistance](PolygonRef poly) { return poly.shorterThan(snapDistance); });
polygons.erase(it, polygons.end());
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--;
}
}
//Finally optimize all the polygons. Every point removed saves time in the long run.
polygons.simplify();
polygonList.simplify();
polygons.removeDegenerateVerts(); // remove verts connected to overlapping line segments
polygonList.removeDegenerateVerts(); // remove verts connected to overlapping line segments
int xy_offset = mesh->getSettingInMicrons("xy_offset");
if (xy_offset != 0)
{
polygons = polygons.offset(xy_offset);
polygonList = polygonList.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);
@@ -504,26 +368,16 @@ Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool kee
// on the slice would create two segments
continue;
}
layers[layer_nr].face_idx_to_segment_idx.insert(std::make_pair(mesh_idx, layers[layer_nr].segments.size()));
layers[layer_nr].face_idx_to_segment_index.insert(std::make_pair(mesh_idx, layers[layer_nr].segmentList.size()));
s.faceIndex = mesh_idx;
s.addedToPolygon = false;
layers[layer_nr].segments.push_back(s);
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);
}
int surface_thickness = mesh->getSettingInMicrons("magic_surface_thickness");
if (surface_thickness)
{
for (SlicerLayer& layer : layers)
{
layer.polygons = layer.polygons.unionPolygons(layer.openPolylines.offsetPolyLine(surface_thickness / 2));
layer.openPolylines.clear();
}
}
}
}//namespace cura
+91 -72
Ver Arquivo
@@ -39,90 +39,109 @@ public:
class SlicerLayer
{
public:
std::vector<SlicerSegment> segments;
std::unordered_map<int, int> face_idx_to_segment_idx; // topology
std::vector<SlicerSegment> segmentList;
std::unordered_map<int, int> face_idx_to_segment_index; // topology
int z;
Polygons polygons;
Polygons polygonList;
Polygons openPolylines;
void makePolygons(Mesh* mesh, bool keepNoneClosed, bool extensiveStitching);
/*!
* 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);
private:
GapCloserResult 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);
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);
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;
}
/*!
* 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);
ClosePolygonResult 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;
}
};
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);
+49 -105
Ver Arquivo
@@ -3,8 +3,7 @@
#include <cmath> // sqrt
#include <utility> // pair
#include <deque>
#include "progress/Progress.h"
#include "Progress.h"
namespace cura
{
@@ -51,7 +50,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
if (mesh.getSettingBoolean("support_roof_enable"))
{
generateSupportRoofs(storage, supportAreas, layer_count, storage.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"));
generateSupportRoofs(storage, supportAreas, layer_count, mesh.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"));
}
else
{
@@ -86,7 +85,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
// given settings
ESupportType support_type = storage.getSettingAsSupportType("support_type");
ESupportType support_type = mesh.getSettingAsSupportType("support_type");
if (!mesh.getSettingBoolean("support_enable"))
return;
@@ -95,6 +94,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
double supportAngle = mesh.getSettingInAngleRadians("support_angle");
bool supportOnBuildplateOnly = support_type == ESupportType::PLATFORM_ONLY;
int supportZDistance = mesh.getSettingInMicrons("support_z_distance");
int supportZDistanceBottom = mesh.getSettingInMicrons("support_bottom_distance");
int supportZDistanceTop = mesh.getSettingInMicrons("support_top_distance");
int join_distance = mesh.getSettingInMicrons("support_join_distance");
@@ -112,41 +112,36 @@ 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 = storage.getSettingInMicrons("layer_height");
int extrusionWidth = storage.getSettingInMicrons("support_line_width");
int layerThickness = mesh.getSettingInMicrons("layer_height");
int extrusionWidth = mesh.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");
if (conical_support_angle == 0)
{
conical_support = false;
}
// derived settings:
if (supportZDistanceBottom < 0) supportZDistanceBottom = supportZDistance;
if (supportZDistanceTop < 0) supportZDistanceTop = supportZDistance;
int supportLayerThickness = layerThickness;
int layerZdistanceTop = std::max(0, supportZDistanceTop / supportLayerThickness) + 1; // support must always be 1 layer below overhang
int layerZdistanceTop = supportZDistanceTop / supportLayerThickness + 1; // support must always be 1 layer below overhang
unsigned int layerZdistanceBottom = std::max(0, supportZDistanceBottom / supportLayerThickness);
double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle
int max_dist_from_lower_layer = tanAngle * supportLayerThickness; // max dist which can be bridged
int maxDistFromLowerLayer = tanAngle * supportLayerThickness; // max dist which can be bridged
int64_t conical_support_offset;
if (conical_support_angle > 0)
{ // outward ==> wider base than overhang
conical_support_offset = -(tan(conical_support_angle) - 0.01) * supportLayerThickness;
{
conical_support_offset = (tan(conical_support_angle) - 0.01) * supportLayerThickness;
}
else
{ // inward ==> smaller base than overhang
conical_support_offset = (tan(-conical_support_angle) - 0.01) * supportLayerThickness;
{
conical_support_offset = -(tan(-conical_support_angle) - 0.01) * supportLayerThickness;
}
unsigned int support_layer_count = layer_count;
@@ -169,27 +164,45 @@ 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)
overhang = basic_and_full_overhang_above.back().second;
basic_and_full_overhang_above.pop_back();
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
*/
}
Polygons& supportLayer_this = overhang;
@@ -229,29 +242,13 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
// inset using X/Y distance
if (supportLayer_this.size() > 0)
{
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));
}
}
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 = std::max(storage.support.layer_nr_max_filled_layer, (int)layer_idx);
storage.support.layer_nr_max_filled_layer = layer_idx;
still_in_upper_empty_layers = false;
}
@@ -266,26 +263,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
{
Polygons& supportLayer = supportAreas[layer_idx];
if (conical_support)
{ // with conical support the next layer is allowed to be larger than the previous
touching_buildplate = touching_buildplate.offset(std::abs(conical_support_offset) + 10, ClipperLib::jtMiter, 10);
// + 10 and larger miter limit cause performing an outward offset after an inward offset can disregard sharp corners
//
// conical support can make
// layer above layer below
// v v
// | : |
// | ==> : |__
// |____ :....
//
// a miter limit would result in
// | : : |
// | :.. <== : |__
// .\___ :....
//
}
touching_buildplate = supportLayer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease!
supportAreas[layer_idx] = touching_buildplate;
@@ -293,39 +270,6 @@ 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,
-18
Ver Arquivo
@@ -75,24 +75,6 @@ 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.
+317
Ver Arquivo
@@ -0,0 +1,317 @@
/*
* The contnts of this file may be overwritten at any time!
*/
#include <iostream>
#include <cstdlib> // rand
/*
#include "utils/intpoint.h"
#include "utils/polygon.h"
// Test whether polygon.inside(point) returns correct results.
void test_poly_inside_and_centerOfMass()
{
{
Polygon poly;
poly.add(Point(2000,2000)); // /
poly.add(Point(1000,1000)); // / /
poly.add(Point(1100,100)); // |/
assert (!poly.inside(Point(-2000,1000)));
assert (poly.inside(Point(1010,1000)));
assert (!poly.inside(Point(5000,1000)));
assert (poly.inside(Point(1111,1100)));
assert (!poly.inside(Point(2001,2001)));
assert (poly.inside(Point(1999,1998)));
std::cerr << "poly.centerOfMass() = " << poly.centerOfMass() << std::endl;
Point center = poly.centerOfMass();
for (int i = 0 ; i < 1000; i++)
{
Point translation(rand()%4000 - 2000, rand()%4000 - 2000);
Polygon translated;
for (Point& p : poly)
{
translated.add(p + translation);
}
Point translated_center = center + translation;
if (vSize2(translated.centerOfMass() - translated_center) > 5*5)
{
std::cerr << "ERROR! test failed! " << std::endl;
std::cerr << "translated.centerOfMass() = " << translated.centerOfMass() << std::endl;
std::cerr << "translated_center = " << translated_center << std::endl;
}
}
}
{
Polygon poly;
poly.add(Point(0,0));
poly.add(Point(100,0)); //
poly.add(Point(100,100)); // |\ /|
poly.add(Point(50,50)); // | \/ |
poly.add(Point(0,100)); // |____|
assert (poly.inside(Point(60,50)));
assert (!poly.inside(Point(50,60)));
assert (poly.inside(Point(60,40)));
assert (poly.inside(Point(50,40)));
assert (!poly.inside(Point(-1,100)));
assert (!poly.inside(Point(-10,-10)));
std::cerr << "poly.centerOfMass() = " << poly.centerOfMass() << std::endl;
Point center = poly.centerOfMass();
for (int i = 0 ; i < 1000; i++)
{
Point translation(rand()%4000 - 2000, rand()%4000 - 2000);
Polygon translated;
for (Point& p : poly)
{
translated.add(p + translation);
}
Point translated_center = center + translation;
if (vSize2(translated.centerOfMass() - translated_center) > 5*5)
{
std::cerr << "ERROR! test failed! " << std::endl;
std::cerr << "translated.centerOfMass() = " << translated.centerOfMass() << std::endl;
std::cerr << "translated_center = " << translated_center << std::endl;
}
}
}
{
Polygon poly;
poly.add(Point( 0,2000)); // |\ .
poly.add(Point( 0, 0)); // | > .
poly.add(Point(1000,1000)); // |/
assert (poly.inside(Point(500,1000)));
assert (poly.inside(Point(200,500)));
assert (poly.inside(Point(200,1500)));
assert (poly.inside(Point(800,1000)));
assert (!poly.inside(Point(-10,1000)));
assert (!poly.inside(Point(1100,1000)));
assert (!poly.inside(Point(600,500)));
assert (!poly.inside(Point(600,1500)));
assert (!poly.inside(Point(2000,1000)));
std::cerr << "poly.centerOfMass() = " << poly.centerOfMass() << std::endl;
Point center = poly.centerOfMass();
for (int i = 0 ; i < 1000; i++)
{
Point translation(rand()%4000 - 2000, rand()%4000 - 2000);
Polygon translated;
for (Point& p : poly)
{
translated.add(translation - Point(-p.X, p.Y));
}
Point translated_center = translation - Point(-center.X, center.Y);
if (vSize2(translated.centerOfMass() - translated_center) > 5*5)
{
std::cerr << "ERROR! test failed! " << std::endl;
std::cerr << "translated.centerOfMass() = " << translated.centerOfMass() << std::endl;
std::cerr << "translated_center = " << translated_center << std::endl;
}
}
}
}*/
/*
struct LocationItem
{
Point p;
int i;
LocationItem(Point p, int i) : p(p), i(i) {};
LocationItem() : p(0,0), i(-1) {};
};
#include "utils/BucketGrid2D.h"
void test_BucketGrid2D()
{
BucketGrid2D<LocationItem> bg(1000);
for (int i = 0; i < 20000; i++)
{
Point p(rand()%100000, rand()%100000);
LocationItem li(p, i);
bg.insert(p, li);
}
// {Point p(00,00); int i = 1; bg.insert(p, i);}
// {Point p(05,05); int i = 2; bg.insert(p, i);}
// {Point p(14,15); int i = 3; bg.insert(p, i);}
// {Point p(25,25); int i = 4; bg.insert(p, i);}
// {Point p(39,39); int i = 5; bg.insert(p, i);}
// {Point p(300,300); int i = 6; bg.insert(p, i);}
Point to(rand()%100000,rand()%100000);
std::cerr << to << std::endl;
LocationItem result;
if (bg.findNearestObject(to, result))
{
std::cerr << "best: " << result.p << std::endl;
}
else
{
std::cerr << "nothing found!" << std::endl;
}
//bg.debug();
}*/
/*
#include <math.h>
#include "utils/gettime.h"
#include "utils/polygonUtils.h"
void test_findClosestConnection()
{
srand(1234);
if (false)
{
Polygon poly2;
poly2.add(Point(0,300));
poly2.add(Point(100,300)); // ____
poly2.add(Point(100,200)); // | |
poly2.add(Point(50,250)); // | /\ |
poly2.add(Point(0,200)); // |/ \|
Polygon poly1;
poly1.add(Point(0,0));
poly1.add(Point(100,0)); //
poly1.add(Point(100,100)); // |\ /|
poly1.add(Point(50,50)); // | \/ |
poly1.add(Point(0,100)); // |____|
ClosestPolygonPoint result1 (poly1);
ClosestPolygonPoint result2 (poly2);
findSmallestConnection(result1, result2, 3);
std::cerr << result1.location << " -- " << result2.location << std::endl;
}
if (false)
{
Polygon poly2;
poly2.add(Point(0,300));
poly2.add(Point(100,300)); // ____
poly2.add(Point(100,200)); // | |
poly2.add(Point(50,250)); // | /\ |
poly2.add(Point(10,105)); // |/ \|
Polygon poly1;
poly1.add(Point(0,0));
poly1.add(Point(100,0)); //
poly1.add(Point(100,100)); // |\ /|
poly1.add(Point(50,50)); // | \/ |
poly1.add(Point(0,100)); // |____|
ClosestPolygonPoint result1 (poly1);
ClosestPolygonPoint result2 (poly2);
findSmallestConnection(result1, result2, 3);
std::cerr << result1.location << " -- " << result2.location << std::endl;
}
double creationTime = 0;
double evalTime = 0;
long totalLength = 0;
TimeKeeper timer;
for (int i = 0; i < 10000; i++)
{ // for vizualization as csv with e.g. Rstudio
Polygon poly1;
double dist = 100;
for (double a = 0; a < 360; a += 1)
{
dist += int(rand()%3) -1;
Point p(static_cast<int>(dist * std::cos(a/180.0*3.1415)), static_cast<int>(dist * std::sin(a/180.0*3.1415)));
p = p + Point(0, 200);
if ( a ==0)
poly1.add(p);
else
poly1.add((poly1.back() + p) / 2);
// std::cerr << poly1.back().X << ", " << poly1.back().Y << std::endl;
}
// std::cerr << " " << std::endl;
Polygon poly2;
dist = 100;
for (double a = 0; a < 360; a += 1)
{
dist += int(rand()%3) - 1;
Point p(static_cast<int>(dist * std::cos(a/180.0*3.1415)), static_cast<int>(dist * std::sin(a/180.0*3.1415)));
if ( a ==0)
poly2.add(p);
else
poly2.add((poly2.back() + p) / 2);
// std::cerr << poly2.back().X << ", " << poly2.back().Y << std::endl;
}
creationTime += timer.restart();
ClosestPolygonPoint result1 (poly1);
ClosestPolygonPoint result2 (poly2);
findSmallestConnection(result1, result2, 240);
totalLength += vSize(result1.location - result2.location);
evalTime += timer.restart();
// std::cerr << " " << std::endl;
// std::cerr << result1.location.X << " , " << result1.location.Y << std::endl;
// std::cerr << result2.location.X << " , " << result2.location.Y << std::endl;
// std::cerr << " " << std::endl;
}
std::cerr << "creationTime : " << creationTime << std::endl;
std::cerr << "evalTime : " << evalTime << std::endl;
std::cerr << "totalLength : " << totalLength << std::endl;
}
*/
#include "utils/polygon.h"
namespace cura
{
void test_clipper()
{
Polygon p;
p.emplace_back(0, 11004);
p.emplace_back(0, 10129);
p.emplace_back(0, 9185);
p.emplace_back(0, 8477);
p.emplace_back(1, 8491);
p.emplace_back(418, 8861);
p.emplace_back(1080, 9389);
p.emplace_back(2106, 10142);
p.emplace_back(3000, 10757);
p.emplace_back(3000, 12010);
p.emplace_back(3000, 12790);
p.emplace_back(3000, 13485);
p.emplace_back(3000, 14088);
p.emplace_back(3000, 14601);
p.emplace_back(3000, 15354);
p.emplace_back(3000, 24867);
p.emplace_back(3000, 25469);
p.emplace_back(3000, 26303);
p.emplace_back(3000, 27421);
p.emplace_back(3000, 28242);
p.emplace_back(2107, 28856);
p.emplace_back(1080, 29610);
p.emplace_back(608, 29986);
p.emplace_back(1, 30508);
p.emplace_back(1, 30522);
p.emplace_back(0, 11772);
Polygons polys;
polys.add(p);
// polys.debugOutputHTML("output/problem_test.html", true);
// polys.offset(-400).debugOutputHTML("output/problem_test_offset.html", true);
polys.removeDegenerateVerts();
// polys.offset(-400).debugOutputHTML("output/problem_test_offset_solved.html", true);
}
int main(int argc, char **argv)
{
// test_findClosestConnection();
test_clipper();
}
}//namespace cura
+11 -15
Ver Arquivo
@@ -9,6 +9,14 @@ namespace cura
#define MINIMUM_PLANNER_SPEED 0.05// (mm/sec)
const double max_feedrate[TimeEstimateCalculator::NUM_AXIS] = {600, 600, 40, 25};
const double minimumfeedrate = 0.01;
const double acceleration = 3000;
const double max_acceleration[TimeEstimateCalculator::NUM_AXIS] = {9000,9000,100,10000};
const double max_xy_jerk = 20.0;
const double max_z_jerk = 0.4;
const double max_e_jerk = 5.0;
template<typename T> const T square(const T& a) { return a * a; }
void TimeEstimateCalculator::setPosition(Position newPos)
@@ -21,15 +29,6 @@ void TimeEstimateCalculator::addTime(double time)
extra_time += time;
}
void TimeEstimateCalculator::setAcceleration(double acc)
{
acceleration = acc;
}
void TimeEstimateCalculator::setMaxXyJerk(double jerk)
{
max_xy_jerk = jerk;
}
void TimeEstimateCalculator::reset()
{
@@ -66,13 +65,10 @@ static inline double intersection_distance(double initial_rate, double final_rat
// This function gives the time it needs to accelerate from an initial speed to reach a final distance.
static inline double acceleration_time_from_distance(double initial_feedrate, double distance, double acceleration)
{
double discriminant = square(initial_feedrate) - 2 * acceleration * -distance;
//If discriminant is negative, we're moving in the wrong direction.
//Making the discriminant 0 then gives the extremum of the parabola instead of the intersection.
discriminant = std::max(0.0, discriminant);
return (-initial_feedrate + sqrt(discriminant)) / acceleration;
double discriminant = sqrt(square(initial_feedrate) - 2 * acceleration * -distance);
return (-initial_feedrate + discriminant) / acceleration;
}
// Calculates trapezoid parameters so that the entry- and exit-speed is compensated by the provided factors.
void TimeEstimateCalculator::calculate_trapezoid_for_block(Block *block, double entry_factor, double exit_factor)
{
+6 -11
Ver Arquivo
@@ -21,7 +21,6 @@ public:
const static unsigned int Z_AXIS = 2;
const static unsigned int E_AXIS = 3;
class Position
{
public:
@@ -55,14 +54,7 @@ public:
};
private:
double max_feedrate[NUM_AXIS] = {600, 600, 40, 25};
double minimumfeedrate = 0.01;
double acceleration = 3000;
double max_acceleration[NUM_AXIS] = {9000, 9000, 100, 10000};
double max_xy_jerk = 20.0;
double max_z_jerk = 0.4;
double max_e_jerk = 5.0;
double extra_time = 0.0;
double extra_time;
Position previous_feedrate;
double previous_nominal_feedrate;
@@ -71,11 +63,14 @@ private:
std::vector<Block> blocks;
public:
TimeEstimateCalculator()
: extra_time(0.0)
{
}
void setPosition(Position newPos);
void plan(Position newPos, double feedRate);
void addTime(double time);
void setAcceleration(double acc); //!< Set the default acceleration to \p acc
void setMaxXyJerk(double jerk); //!< Set the max xy jerk to \p jerk
void reset();
double calculate();
-70
Ver Arquivo
@@ -1,70 +0,0 @@
/** 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(const Point&min, const 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
+127 -20
Ver Arquivo
@@ -1,8 +1,8 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef UTILS_AABB_H
#define UTILS_AABB_H
#ifndef AABB_H
#define AABB_H
#include <limits>
#include "intpoint.h"
#include "polygon.h"
@@ -17,22 +17,79 @@ class AABB
public:
Point min, max;
AABB(); //!< initializes with invalid min and max
AABB(const Point& min, const Point& max); //!< initializes with given min and max
AABB(const Polygons& polys); //!< Computes the boundary box for the given polygons
void calculate(const Polygons& polys); //!< Calculates the aabb for the given polygons (throws away old min and max data of this aabb)
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);
}
/*!
* Check whether this aabb overlaps with another.
* \brief Creates an axis aligned bounding box around the specified polygon.
*
* In the boundary case false is returned.
* The bounding box will fit snugly around the polygon.
*
* \param other the aabb to check for overlaps with
* \return Whether the two aabbs overlap
* \param polygon The polygon to create a bounding box for.
*/
bool hit(const AABB& other) const;
AABB(PolygonRef& polygon)
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
{
for(Point point : polygon)
{
include(point);
}
}
/*!
* \brief Creates an axis aligned bounding box around the specified set of
* polygons.
*
* The bounding box will fit around all polygons.
*
* \param polygons A vector of polygons that should be included in the
* bounding box.
*/
AABB(std::vector<PolygonRef>& polygons)
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
{
for(PolygonRef polygon : polygons)
{
for(Point point : polygon)
{
include(point);
}
}
}
void calculate(Polygons& polys)
{
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 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.
*
@@ -40,16 +97,66 @@ public:
*
* \param point The point to include in the bounding box.
*/
void include(Point point);
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);
}
};
/*!
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
/*!
* 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
* Create an AABB3D with coordinates at the numeric limits.
*/
void expand(int dist);
AABB3D()
: min(std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max())
, max(std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min())
{
}
/*!
* Expand the AABB3D to include the point \p p.
* \param p The point to include with the bounding box.
*/
void include(Point3 p)
{
min.x = std::min(min.x, p.x);
min.y = std::min(min.y, p.y);
min.z = std::min(min.z, p.z);
max.x = std::max(max.x, p.x);
max.y = std::max(max.y, p.y);
max.z = std::max(max.z, p.z);
}
/*!
* Offset the coordinates of the bounding box.
* \param offset The offset with which to offset the AABB3D.
*/
void offset(Point3 offset)
{
min += offset;
max += offset;
}
/*!
* Offset the coordinates of the bounding box.
* \param offset The offset with which to offset the AABB3D.
*/
void offset(Point offset)
{
min += offset;
max += offset;
}
};
}//namespace cura
#endif//UTILS_AABB_H
#endif//AABB_H
-50
Ver Arquivo
@@ -1,50 +0,0 @@
/** 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
-56
Ver Arquivo
@@ -1,56 +0,0 @@
/** 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
+18 -66
Ver Arquivo
@@ -2,11 +2,9 @@
#ifndef UTILS_BUCKET_GRID_2D_H
#define UTILS_BUCKET_GRID_2D_H
#include <unordered_map>
#include <functional> // std::function
#include "logoutput.h"
#include "intpoint.h"
#include <unordered_map>
namespace cura
{
@@ -15,31 +13,22 @@ namespace cura
* Container for items with location for which the lookup for nearby items is optimized.
*
* It functions by hashing the items location and lookuping up based on the hash of that location and the hashes of nearby locations.
*
* We're mapping a cell location multiple times to an object within the cell,
* instead of mapping each cell location only once to a vector of objects within the cell.
*
* The first (current) implementation has the overhead of 'bucket-collisions' where all mappings of two different cells get placed in the same bucket,
* which causes findNearby to loop over unneeded elements.
* The second (alternative) implementation has the overhead and indirection of creating vectors and all that comes with it."
*
*/
template<typename T>
class BucketGrid2D
{
private:
typedef Point Cellidx;
/*!
* Returns a point for which the hash is at a grid position of \p relative_hash relative to \p p.
* Returns a point for which the hash is at a grid position of \p relativeHash relative to \p p.
*
* \param p The point for which to get the relative point to hash
* \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.
* \param relativeHash The relative position - in grid terms - of the relative point.
* \return A point for which the hash is at a grid position of \p relativeHash relative to \p p.
*/
inline Point getRelativeForHash(const Point& p, const Cellidx& relative_hash) const
inline Point getRelativeForHash(const Point& p, const Point& relativeHash)
{
return p + relative_hash * squareSize;
return p + relativeHash*squareSize;
}
@@ -54,7 +43,7 @@ private:
* \param p The grid location to hash
* \return the hash
*/
inline uint32_t pointHash_simple(const Cellidx& p) const
inline uint32_t pointHash_simple(const Point& p) const
{
return p.X ^ (p.Y << 8);
}
@@ -66,13 +55,13 @@ private:
*/
inline uint32_t pointHash(const Point& point) const
{
Cellidx p = point / squareSize;
Point 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);
}*/
@@ -112,10 +101,6 @@ private:
* \param squareSize The horizontal and vertical size of a cell in the grid; the width and height of a bucket.
*/
int squareSize;
PointHasher point_hasher; //!< The hasher used by the unordered_map
int max_load_factor; //!< The average number of elements per cell/bucket
/*!
* The map type used to associate points with their objects.
*/
@@ -132,25 +117,10 @@ public:
* The constructor for a bucket grid.
*
* \param squareSize The horizontal and vertical size of a cell in the grid; the width and height of a bucket.
* \param initial_map_size The number of elements to be inserted
* \param initial_map_size The minimal number of initial buckets
*/
BucketGrid2D(int squareSize, unsigned int initial_map_size = 4)
: squareSize(squareSize)
, point_hasher(squareSize)
, max_load_factor(2)
, point2object(initial_map_size / max_load_factor, point_hasher)
{
point2object.max_load_factor(max_load_factor); // we expect each cell to contain at least two points on average
point2object.reserve(initial_map_size);
}
BucketGrid2D(int squareSize, unsigned int initial_map_size = 4) : squareSize(squareSize), point2object(initial_map_size, PointHasher(squareSize)) {};
/*!
* Get the size (height, width) of the cells.
*/
int getCellSize() const
{
return squareSize;
}
/*!
* Find all objects with a point in a grid cell at a distance of one cell from the cell of \p p.
*
@@ -159,20 +129,16 @@ 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) const
void findNearbyObjects(Point& p, std::vector<T>& ret)
{
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
Point relative_point = getRelativeForHash(p, Point(x,y));
int bucket_idx = point2object.bucket(relative_point); // when the hash is not a hash of a present item, the bucket_idx returned may be one already encountered
int bucket_idx = point2object.bucket(getRelativeForHash(p, Point(x,y))); // when the hash is not a hash of a present item, the bucket_idx returned may be one already encountered
for ( auto local_it = point2object.begin(bucket_idx); local_it!= point2object.end(bucket_idx); ++local_it )
{
if (point_hasher(relative_point) == point_hasher(local_it->first))
{
ret.push_back(local_it->second);
}
ret.push_back(local_it->second);
}
}
}
@@ -186,27 +152,24 @@ public:
* \param p The point for which to find close points.
* \return All objects close to \p p.
*/
std::vector<T> findNearbyObjects(Point& p) const
std::vector<T> findNearbyObjects(Point& p)
{
std::vector<T> ret;
findNearbyObjects(p, ret);
return ret;
}
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.
*
* \param p The point for which to find the nearest object.
* \param nearby Output parameter: the nearest object, if any
* \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, const T& object)> precondition = no_precondition) const
bool findNearestObject(Point& p, T& nearby)
{
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++)
@@ -214,10 +177,6 @@ public:
int bucket_idx = point2object.bucket(getRelativeForHash(p, Point(x,y)));
for ( auto local_it = point2object.begin(bucket_idx); local_it!= point2object.end(bucket_idx); ++local_it )
{
if (!precondition(local_it->first, local_it->second))
{
continue;
}
int32_t dist2 = vSize2(local_it->first - p);
if (dist2 < bestDist2)
{
@@ -238,7 +197,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 =
@@ -246,14 +205,7 @@ public:
// if (! emplaced.second)
// logError("Error! BucketGrid2D couldn't insert object!");
};
};
template<typename T>
const std::function<bool(Point, const T&)> BucketGrid2D<T>::no_precondition = [](Point loc, const T&) { return true; };
}//namespace cura
#endif//BUCKET_GRID_2D_H
-52
Ver Arquivo
@@ -1,52 +0,0 @@
/** 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

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais