Comparar commits

..

7 Commits

Autor SHA1 Mensagem Data
Tim Kuipers 41f7258e25 bugfix: retraction count max 2015-08-05 14:30:57 +02:00
Tim Kuipers 24cb4fbb36 bugfix for combined infill lines 2015-07-28 14:00:51 +02:00
Tim Kuipers 6fa022013d bugfix: infill overlap for 100% infill 2015-07-22 15:30:45 +02:00
Tim Kuipers 9274846970 merge 2015-07-22 13:03:54 +02:00
Tim Kuipers 9050f8da53 cleanup debug shite last bugfix 2015-07-20 14:37:59 +02:00
Tim Kuipers f7039848c2 bugfix: remove degenerate verts: verts connected to overlapping line segments 2015-07-20 14:18:14 +02:00
Arjen Hiemstra d4e27d8bbb Force using clang libc++ on MacOSX to prevent issues with linking 2015-07-16 15:58:00 +02:00
167 arquivos alterados com 6250 adições e 1922003 exclusões
+13 -22
Ver Arquivo
@@ -1,34 +1,25 @@
*.tar.bz2
*.tar.gz
*.7z
*.zip
.DS_Store
*~
NUL
*.gcode
## Building result.
build/*
*.pyc
*.zip
*.exe
*.o
CuraEngine
.idea
.DS_Store
_bin
_obj
## IDE project files.
*.depend
*.o
.*.swp
*.gcode
CuraEngine
build/*
*~
NUL
CuraEngine.layout
CuraEngine.cbp
*kdev*
*.kate-swp
nbproject/*
.idea
*.depend
.*.swp
## Documentation.
documentation/html/*
documentation/latex/*
## Test results.
tests/output.xml
*kdev*
*.kate-swp
+15 -103
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)
@@ -17,143 +10,62 @@ else()
set(CMAKE_CXX_FLAGS "-std=c++11")
endif()
if(APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++")
endif()
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
set(CURA_ENGINE_VERSION "master" CACHE STRING "Version name of Cura")
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)
endif()
# Add warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
if(NOT APPLE AND NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++")
elseif(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR} libs)
add_library(clipper STATIC libs/clipper/clipper.cpp)
set(engine_SRCS # Except main.cpp.
set(engine_SRCS
src/bridge.cpp
src/comb.cpp
src/commandSocket.cpp
src/FffGcodeWriter.cpp
src/FffPolygonGenerator.cpp
src/FffProcessor.cpp
src/gcodeExport.cpp
src/gcodePlanner.cpp
src/infill.cpp
src/inset.cpp
src/LayerPlanBuffer.cpp
src/Material.cpp
src/MaterialBase.cpp
src/MergeInfillLines.cpp
src/layerPart.cpp
src/main.cpp
src/mesh.cpp
src/MeshGroup.cpp
src/multiVolumes.cpp
src/pathOrderOptimizer.cpp
src/PrimeTower.cpp
src/Progress.cpp
src/polygonOptimizer.cpp
src/raft.cpp
src/settingRegistry.cpp
src/settings.cpp
src/skin.cpp
src/skirt.cpp
src/sliceDataStorage.cpp
src/slicer.cpp
src/support.cpp
src/timeEstimate.cpp
src/TexturedMesh.cpp
src/TextureProcessor.cpp
src/wallOverlap.cpp
src/Weaver.cpp
src/Wireframe2gcode.cpp
src/infill/NoZigZagConnectorProcessor.cpp
src/infill/ZigzagConnectorProcessorConnectedEndPieces.cpp
src/infill/ZigzagConnectorProcessorDisconnectedEndPieces.cpp
src/infill/ZigzagConnectorProcessorEndPieces.cpp
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
src/slicer/LayerPart.cpp
src/slicer/MultiVolumes.cpp
src/slicer/SlicerLayer.cpp
src/slicer/Slicer.cpp
src/modelFile/modelFile.cpp
src/utils/gettime.cpp
src/utils/LinearAlg2D.cpp
src/utils/logoutput.cpp
src/utils/polygonUtils.cpp
src/utils/polygon.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
LinearAlg2DTest
)
# Generating ProtoBuf protocol
if (ENABLE_ARCUS)
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 ()
add_executable(CuraEngine ${engine_SRCS} ${engine_PB_SRCS})
target_link_libraries(CuraEngine clipper Arcus)
set_target_properties(_CuraEngine PROPERTIES COMPILE_DEFINITIONS "VERSION=\"${CURA_ENGINE_VERSION}\"")
add_executable(Test src/test.cpp)
target_link_libraries(Test clipper)
if (UNIX)
target_link_libraries(_CuraEngine pthread)
endif()
add_executable(CuraEngine src/main.cpp) #Then compile main.cpp as separate executable, and link the library to it.
target_link_libraries(CuraEngine _CuraEngine)
# Compiling the test environment.
if (BUILD_TESTS)
message(STATUS "Building tests...")
enable_testing()
foreach (test ${engine_TEST})
add_executable(${test} tests/main.cpp tests/${test}.cpp)
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()
target_link_libraries(CuraEngine pthread)
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})
include(CPackConfig.cmake)
-20
Ver Arquivo
@@ -1,20 +0,0 @@
set(CPACK_PACKAGE_VENDOR "Ultimaker")
set(CPACK_PACKAGE_CONTACT "Arjen Hiemstra <a.hiemstra@ultimaker.com>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cura Engine")
set(CPACK_PACKAGE_VERSION "15.05.90")
set(CPACK_GENERATOR "DEB")
if(NOT DEFINED CPACK_DEBIAN_PACKAGE_ARCHITECTURE)
execute_process(COMMAND dpkg --print-architecture OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
set(DEB_DEPENDS
"arcus (>= 15.05.90)"
"protobuf (>= 3.0.0)"
"libstdc++6 (>= 4.9.0)"
"libgcc1 (>= 4.9.0)"
)
string(REPLACE ";" ", " DEB_DEPENDS "${DEB_DEPENDS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS ${DEB_DEPENDS})
include(CPack)
-14
Ver Arquivo
@@ -1,14 +0,0 @@
Changelog CuraEngine
====================
- Feature: infill_wipe_dist. Add a travel move after an infill line in order to let it stick better to the walls.
- Feature: Draft Protection Screen. A shell similar to the ooze shield providing protection from gusts of wind and acting similar to a heated chamber
Release 15.06.01
-----
- [Not documented]
+7 -25
Ver Arquivo
@@ -1,22 +1,13 @@
syntax = "proto3";
package cura.proto;
message ObjectList
{
repeated Object objects = 1;
repeated Setting settings = 2;
}
package Cura;
// typeid 1
message Slice
{
repeated ObjectList object_lists = 1;
message ObjectList {
repeated Object objects = 1;
}
message Object
{
message Object {
int64 id = 1;
bytes vertices = 2; //An array of 3 floats.
bytes normals = 3; //An array of 3 floats.
@@ -25,19 +16,16 @@ message Object
}
// typeid 3
message Progress
{
message Progress {
float amount = 1;
}
// typeid 2
message SlicedObjectList
{
message SlicedObjectList {
repeated SlicedObject objects = 1;
}
message SlicedObject
{
message SlicedObject {
int64 id = 1;
repeated Layer layers = 2;
@@ -62,8 +50,6 @@ message Polygon {
SkirtType = 5;
InfillType = 6;
SupportInfillType = 7;
MoveCombingType = 8;
MoveRetractionType = 9;
}
Type type = 1;
bytes points = 2;
@@ -98,7 +84,3 @@ message Setting {
message GCodePrefix {
bytes data = 2;
}
// typeid 8
message SlicingFinished {
}
+3 -4
Ver Arquivo
@@ -58,8 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
#LEAVE THIS DIRECTORY EMPTY IF THIS DOCUMENTATION NEEDS TO BE PUBLISHED AUTOMATICALLY TO GITHUB PAGES!
OUTPUT_DIRECTORY = docs
OUTPUT_DIRECTORY = documentation
# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
@@ -178,7 +177,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
@@ -2050,7 +2049,7 @@ HIDE_UNDOC_RELATIONS = YES
# set to NO
# The default value is: NO.
HAVE_DOT = YES
HAVE_DOT = NO
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
+8 -9
Ver Arquivo
@@ -3,9 +3,9 @@ CuraEngine
The CuraEngine is a C++ console application for 3D printing GCode generation. It has been made as a better and faster alternative to the old Skeinforge engine.
The CuraEngine is pure C++ and uses Clipper from http://www.angusj.com/delphi/clipper.php
Furthermore it depends on libArcus by Ultimaker, which can be found at http://github.com/Ultimaker/libArcus
There are no external dependences and Clipper is included in the source code without modifications.
This is just a console application for GCode generation. For a full graphical application look at https://github.com/Ultimaker/Cura which is the graphical frontend for CuraEngine.
This is just a console application for GCode generation. For a full graphical application look at https://github.com/daid/Cura which is the graphical frontend for CuraEngine.
The CuraEngine can be used seperately or in other applications. Feel free to add it to your application. But please take note of the License.
@@ -24,7 +24,6 @@ How to Install
In order to compile CuraEngine, either use CMake or start a project in your preferred IDE.
CMake compilation:
1. Navigate to the CuraEngine directory and execute the following commands
2. $ mkdir build && cd build
3. $ cmake ..
@@ -49,16 +48,16 @@ Installing Protobuf
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.
Other than running CuraEngine from a frontend, such as PluggableCura, one can run CuraEngine from the command line.
An example run for an UM2 machine looks as follows:
* Navigate to the CuraEngine directory and execute the following
```
./build/CuraEngine slice -v -j ../Cura/resources/machines/dual_extrusion_printer.json -o "output/test.gcode" -e1 -s infill_line_distance=0 -e0 -l "/model_1.stl" -e1 -l "fully_filled_model.stl"
./build/CuraEngine -v -j fdmprinter.json -s machine_start_gcode=";FLAVOR:UltiGCode
;TIME:10000000
;MATERIAL:2000
;MATERIAL2:0" -s mesh_position_x=115.0 -s mesh_position_y=112.5 -s mesh_position_z=0 -s material_diameter=1.128 -o "output/test.gcode" "/path/to/model.stl"
```
Run `CuraEngine help` for a general description of how to use the CuraEngine tool.
Internals
=========
@@ -120,4 +119,4 @@ The GCode generation is quite a large bit of code. As a lot is going on here. Im
* PathOrderOptimizer: This piece of code needs to solve a TravelingSalesmanProblem. Given a list of polygons/lines it tries to find the best order in which to print them. It currently does this by finding the closest next polygon to print.
* Infill: This code generates a group of lines from an area. This is the code that generates the actuall infill pattern. There is also a concentric infill function, which is currently not used.
* Comb: The combing code is the code that tries to avoid holes when moving the head around without printing. This code also detects when it fails. The final GCode generator uses the combing code while generating the final GCode. So they interact closely.
* GCodeExport: The GCode export is a 2 step process. First it collects all the paths for a layer that it needs to print, this includes all moves, prints, extrusion widths. And then it generates the final GCode. This is the only piece of code that has knowledge about GCode keywords and syntax to generate a different flavor of GCode it will be the only piece that needs adjustment. All volumatric calculations also happen here.
* GCodeExport: The GCode export is a 2 step process. First it collects all the paths for a layer that it needs to print, this includes all moves, prints, extrusion widths. And then it generates the final GCode. This is the only piece of code that has knowledge about GCode keywords and syntax;meshmdhfdhfdhf to generate a different flavor of GCode it will be the only piece that needs adjustment. All volumatric calculations also happen here.
Arquivo binário não exibido.

Antes

Largura:  |  Altura:  |  Tamanho: 20 KiB

+126
Ver Arquivo
@@ -0,0 +1,126 @@
Code Conventions
=======
Note that the code convention described here have not all yet been fully implemented.
Bracketing and indenting
-----
~~~~~~~~~~~~~~~{.cpp}
if (condition) // brackets always on new lines
{ // allways a bracket after an if, for, while, etc.
// indent always with 4 spaces, never with tabs
}
else // else on new line
{
// more code
}
~~~~~~~~~~~~~~~
Naming conventions
------
* variables: lower_case_with_underscores
* functions: loweCamelCase
* classes: UpperCamelCase
* macros: UPPER_CASE_WITH_UNDERSCORES
~~~~~~~~~~~~~~~{.cpp}
#define UPPER_CASE_MACRO 1
class UpperCamelCase
{
private:
MemberVariableObject with_underscores;
public:
MemberVariableObject with_underscores;
public:
UpperCamelCase();
~UpperCamelCase();
// start with input variable(s) and end with output variable(s)
void lowerCamelCaseFunctions(ParamObject& also_with_underscores)
{
LocalObject under_scores;
}
private:
void putFunctionsAndVariablesInSeperatePublicPrivateBlocks();
};
~~~~~~~~~~~~~~~
Ordering
----
~~~~~~~~~~~~~~~{.cpp}
class Example
{
// start with input variable(s) and end with output parameter(s)
void function1(ParamObject& input_variable, int setting_parameter, ParamObject2& return_parameter)
{
function2();
function3();
}
// place functions called solely by one other function below it chronologically
void function2();
void function3();
};
~~~~~~~~~~~~~~~
Documentation
----
We use [Doxygen](www.doxygen.org/) to generate documentation. Try to keep your documentation in doxygen style.
Here's a small example:
~~~~~~~~~~~~~~~{.cpp}
/ *!
* Doxygen style comments!
*
* \param param1 explanation may refer to another \p param2
* /
void function(int param1, int param2)
{
// non-doxygen style comments on implementation details
}
int member; //!< inline doxygen comment on the entry to the left
~~~~~~~~~~~~~~~
Files
--------
For a file Foo.h (UpperCamelCase):
~~~~~~~~~~~~~~~{.cpp}
#ifndef FOO_H
#define FOO_H
// [content]
#endif//FOO_H
~~~~~~~~~~~~~~~
Other
----
~~~~~~~~~~~~~~~{.cpp}
#include <all>
#include <includes>
#include <on>
#include <top>
#include <first_system_includes>
#include <then_library_includes>
#include "finally_local_includes"
enum class EnumExample
{
ELEM0 = 0,
ELEM1 = 1
}
~~~~~~~~~~~~~~~
Illegal syntax
----
~~~~~~~~~~~~~~~{.cpp}
void function()
{
if (condition)
single_line_outside_code_block(); // always use braces!
}; // unneccesary semicolon after function definition is not allowed
~~~~~~~~~~~~~~~
-1
Ver Arquivo
@@ -4,7 +4,6 @@ Glossary
Term/Synonyms | Meaning
--- | ---
Extruder Train | The whole of a feeder, bowden tube and a nozzle
Island/Part | isolated/unconnected part in 2D slice
Inset | perimeter, the perimeters which are laid down around the infill
Slicing | The act of extracting the contours of the object at a certain height (not the whole process which would also include gcode generation etc.)
+1325
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-57
Ver Arquivo
@@ -1,57 +0,0 @@
Tencent is pleased to support the open source community by making RapidJSON available.
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License.
If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON.
A copy of the MIT License is included in this file.
Other dependencies and licenses:
Open Source Software Licensed Under the BSD License:
--------------------------------------------------------------------
The msinttypes r29
Copyright (c) 2006-2013 Alexander Chemeris
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Open Source Software Licensed Under the JSON License:
--------------------------------------------------------------------
json.org
Copyright (c) 2002 JSON.org
All Rights Reserved.
JSON_checker
Copyright (c) 2002 JSON.org
All Rights Reserved.
Terms of the JSON License:
---------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Terms of the MIT License:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-203
Ver Arquivo
@@ -1,203 +0,0 @@
This file is a conversion from the ENGINE settings of 15.04 to the ENGINE setting of 2.0
This is NOT a conversion on the frontend internal setting names (15.04 has the dictionary of doom)
autoCenter ? ==> center_object OR machine_center_is_zero ??
coolHeadLift ==> cool_lift_head
downSkinCount ==> bottom_layers
enableCombing ==> retraction_combing
enableOozeShield ==> ooze_shield_enabled
endCode ==> machine_end_gcode
extruderOffset[MAX_EXTRUDERS] = (machine_nozzle_offset_x, machine_nozzle_offset_y)
extrusionWidth ==> infill_line_width, skirt_line_width, support_line_width
fanFullOnLayerNr ==> cool_fan_full_layer
fanSpeedMax ==> cool_fan_speed_max
fanSpeedMin ==> cool_fan_speed_min
filamentDiameter ==> material_diameter
filamentFlow ==> material_flow
fixHorrible ==> meshfix_union_all AND/OR meshfix_union_all_remove_holes AND/OR meshfix_extensive_stitching AND/OR magic_mesh_surface_mode
gcodeFlavor ==> machine_gcode_flavor
infillOverlap ==> infill_overlap
infillPattern ==> infill_pattern
infillSpeed ==> speed_infill
initialLayerSpeed ==> speed_layer_0
initialLayerThickness ==> layer_height_0
initialSpeedupLayers ==> speed_slowdown_layers
inset0Speed ==> speed_wall_0
insetCount ==> wall_line_count
insetXSpeed ==> speed_wall_x
layer0extrusionWidth [ Doesn't exist anymore ]
layerThickness ==> layer_height
matrix [ Doesn't exist anymore ]
minimalExtrusionBeforeRetraction
minimalFeedrate ==> cool_min_speed
minimalLayerTime ==> cool_min_layer_time
moveSpeed ==> speed_travel
multiVolumeOverlap ==> multiple_mesh_overlap
nozzleSize ==> machine_nozzle_size
objectPosition ==> mesh_position_x, mesh_position_y, mesh_position_z
objectSink [ Doesn't exist in CuraEngine anymore ]
perimeterBeforeInfill = not(infill_before_walls)
postSwitchExtruderCode ==> machine_extruder_start_code
preSwitchExtruderCode ==> machine_extruder_end_code
printSpeed ==> speed_prime_tower, speed_support_lines, speed_support_roof, skirt_speed
raftAirGap ==> raft_airgap
raftAirGapLayer0 ?!?!?
raftBaseLinewidth ==> raft_base_line_width
raftBaseSpeed ==> raft_interface_speed, raft_base_speed
raftBaseThickness ==> raft_base_thickness
raftFanSpeed ==> raft_base_fan_speed, raft_interface_fan_speed, raft_surface_fan_speed
raftInterfaceLineSpacing==> raft_interface_line_spacing
raftInterfaceLinewidth ==> raft_interface_line_width
raftInterfaceThickness ==> raft_interface_thickness
raftLineSpacing ==> raft_base_line_spacing
raftMargin ==> raft_margin
raftSurfaceLayers ==> raft_surface_layers
raftSurfaceLineSpacing ==> raft_surface_line_spacing
raftSurfaceLinewidth ==> raft_surface_line_width
raftSurfaceSpeed ==> raft_surface_speed
raftSurfaceThickness ==> raft_surface_thickness
retractionAmount ==> retraction_amount (set retraction_enable = true)
retractionAmountExtruderSwitch ==> switch_extruder_retraction_amount
retractionAmountPrime ==> retraction_extra_prime_amount
retractionMinimalDistance ==> retraction_extrusion_window ( set retraction_count_max = 1 )
retractionSpeed ==> retraction_retract_speed (, retraction_prime_speed ?), switch_extruder_retraction_speed
retractionZHop ==> retraction_hop
simpleMode ??!
skinSpeed ==> speed_topbottom
skirtDistance ==> skirt_gap
skirtLineCount ==> brim_line_count, skirt_line_count
skirtMinLength ==> skirt_minimal_length
sparseInfillLineDistance ==> infill_line_distance
spiralizeMode ==> magic_spiralize
startCode ==> machine_start_gcode
supportAngle ==> support_angle, support_enable=true if support_angle>0
supportEverywhere ==> support_type
supportExtruder ==> support_extruder_nr, support_extruder_nr_layer_0
supportLineDistance ==> support_line_distance
supportType ==> support_pattern
supportXYDistance ==> support_xy_distance
supportZDistance ==> support_z_distance
upSkinCount ==> top_layers
wipeTowerSize ==> prime_tower_size
NEW:
adhesion_extruder_nr
adhesion_type
alternate_extra_perimeter
coasting_enable
coasting_min_volume_move
coasting_min_volume_retract
coasting_speed_move
coasting_speed_retract
coasting_volume_move
coasting_volume_retract
cool_min_layer_time_fan_speed_max
draft_shield_dist
draft_shield_height
extruder_nr
fill_perimeter_gaps
infill_sparse_thickness
infill_wipe_dist
machine_depth
machine_extruder_count
machine_extruder_end_pos_abs
machine_extruder_end_pos_x
machine_extruder_end_pos_y
machine_extruder_start_pos_abs
machine_extruder_start_pos_x
machine_extruder_start_pos_y
machine_heated_bed
machine_nozzle_cool_down_speed
machine_nozzle_expansion_angle
machine_nozzle_head_distance
machine_nozzle_heat_up_speed
machine_nozzle_tip_outer_diameter
machine_print_temp_wait
machine_use_extruder_offset_to_offset_coords
machine_width
magic_fuzzy_skin_enabled
magic_fuzzy_skin_point_dist
magic_fuzzy_skin_thickness
material_bed_temperature
material_bed_temp_prepend
material_bed_temp_wait
material_extrusion_cool_down_speed
material_flow_dependent_temperature
material_flow_temp_graph
material_print_temperature
material_print_temp_prepend
material_print_temp_wait
material_standby_temperature
meshfix_keep_open_polygons
ooze_shield_angle
ooze_shield_dist
prime_tower_dir_outward
prime_tower_distance
prime_tower_flow
prime_tower_line_width
prime_tower_position_x
prime_tower_position_y
prime_tower_wipe_enabled
remove_overlapping_walls_0_enabled
remove_overlapping_walls_x_enabled
retraction_min_travel
skin_alternate_rotation
skin_line_width
skin_no_small_gaps_heuristic
skin_outline_count
support_area_smoothing
support_bottom_distance
support_bottom_stair_step_height
support_conical_angle
support_conical_enabled
support_conical_min_width
support_connect_zigzags
support_join_distance
support_minimal_diameter
support_offset
support_roof_enable
support_roof_extruder_nr
support_roof_height
support_roof_line_distance
support_roof_line_width
support_roof_pattern
support_top_distance
support_tower_diameter
support_tower_roof_angle
switch_extruder_prime_speed
top_bottom_pattern
travel_avoid_distance
travel_avoid_other_parts
travel_compensate_overlapping_walls_enabled
wall_line_width_0
wall_line_width_x
wireframe_bottom_delay
wireframe_drag_along
wireframe_enabled
wireframe_fall_down
wireframe_flat_delay
wireframe_flow_connection
wireframe_flow_flat
wireframe_height
wireframe_nozzle_clearance
wireframe_printspeed_bottom
wireframe_printspeed_down
wireframe_printspeed_flat
wireframe_printspeed_up
wireframe_roof_drag_along
wireframe_roof_fall_down
wireframe_roof_inset
wireframe_roof_outer_delay
wireframe_straight_before_down
wireframe_strategy
wireframe_top_delay
wireframe_top_jump
wireframe_up_half_speed
xy_offset
z_seam_type
-130
Ver Arquivo
@@ -1,130 +0,0 @@
[ problem ]
gantry_height introduced by Jaime while there already was machine_nozzle_gantry_distance
[ RENAMES ]
raft_base_linewidth > raft_base_line_width
raft_interface_linewidth > raft_interface_line_width
fill_overlap > infill_overlap
fill_pattern > infill_pattern
fill_sparse_combine > infill_sparse_combine
fill_sparse_density > infill_sparse_density
fill_sparse_thickness > infill_sparse_thickness
support_fill_rate > support_infill_rate
[ SPLITS ]
raft_line_spacing > raft_base_line_spacing (, raft_interface_line_spacing, raft_surface_line_spacing)
wall_overlap_avoid_enabled > remove_overlapping_walls_enabled (, remove_overlapping_walls_0_enabled, remove_overlapping_walls_x_enabled)
retraction_minimal_extrusion > retraction_extrusion_window (+ retraction_count_max = 1)
magic_mesh_surface_mode : false >> "Normal"
magic_mesh_surface_mode : true >> "Surface"
[ NEW ]
alternate_extra_perimeter
coasting_enable
coasting_min_volume
coasting_min_volume_move
coasting_min_volume_retract
coasting_speed
coasting_speed_move
coasting_speed_retract
coasting_volume
coasting_volume_move
coasting_volume_retract
fill_perimeter_gaps
draft_shield_dist
draft_shield_enabled
draft_shield_height
draft_shield_height_limitation
infill_wipe_dist
line_width (was wall_line_width)
machine_extruder_count
machine_head_polygon
machine_head_with_fans_polygon
machine_heat_zone_length
magic_mesh_surface_mode
meshfix_extensive_stitching
meshfix_keep_open_polygons
meshfix_union_all
meshfix_union_all_remove_holes
print_sequence
raft_base_line_spacing (from raft_line_spacing)
raft_base_line_width
raft_fan_speed
raft_interface_fan_speed
raft_interface_line_spacing
raft_interface_speed
raft_speed
raft_surface_fan_speed
raft_surface_line_spacing
raft_surface_line_width
raft_surface_speed
raft_surface_thickness
remove_overlapping_walls_0_enabled
remove_overlapping_walls_enabled
remove_overlapping_walls_x_enabled
retraction_count_max
retraction_extrusion_window (from retraction_minimal_extrusion)
retraction_extra_prime_amount
skin_alternate_rotation
speed_support_lines
speed_support_roof
support_conical_angle
support_conical_enabled
support_conical_min_width
support_offset
support_roof_enable
support_roof_height
support_roof_line_width
travel_avoid_distance
travel_avoid_other_parts
travel_compensate_overlapping_walls_enabled
z_seam_type
[ DUAL EXTRUSION ]
extruder_nr
machine_use_extruder_offset_to_offset_coords
machine_nozzle_offset_x
machine_nozzle_offset_y
machine_extruder_start_code
machine_extruder_start_pos_abs
machine_extruder_start_pos_x
machine_extruder_start_pos_y
machine_extruder_end_pos_abs
machine_extruder_end_pos_x
machine_extruder_end_pos_y
machine_extruder_end_code
prime_tower_enable
prime_tower_size
prime_tower_position_x
prime_tower_position_y
prime_tower_flow
prime_tower_wipe_enabled
ooze_shield_enabled
ooze_shield_angle
ooze_shield_dist
-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
}
}
}
}
}
-23
Ver Arquivo
@@ -1,23 +0,0 @@
#ifndef EXTRUDER_TRAIN_H
#define EXTRUDER_TRAIN_H
#include "settings.h"
namespace cura
{
class ExtruderTrain : public SettingsBase
{
int extruder_nr;
public:
int getExtruderNr() { return extruder_nr; }
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
: SettingsBase(settings)
, extruder_nr(extruder_nr)
{ }
};
}//namespace cura
#endif // EXTRUDER_TRAIN_H
-22
Ver Arquivo
@@ -1,22 +0,0 @@
#ifndef FAN_SPEED_LAYER_TIME_H
#define FAN_SPEED_LAYER_TIME_H
#include "settings.h"
namespace cura
{
struct FanSpeedLayerTimeSettings
{
public:
double cool_min_layer_time;
double cool_min_layer_time_fan_speed_max;
double cool_fan_speed_min;
double cool_fan_speed_max;
double cool_min_speed;
int cool_fan_full_layer;
};
} // namespace cura
#endif // FAN_SPEED_LAYER_TIME_H
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-397
Ver Arquivo
@@ -1,397 +0,0 @@
#ifndef GCODE_WRITER_H
#define GCODE_WRITER_H
#include <fstream>
#include "utils/gettime.h"
#include "utils/logoutput.h"
#include "utils/NoCopy.h"
#include "utils/polygonUtils.h"
#include "sliceDataStorage.h"
#include "raft.h"
#include "infill.h"
#include "bridge.h"
#include "pathOrderOptimizer.h"
#include "gcodePlanner.h"
#include "gcodeExport.h"
#include "commandSocket.h"
#include "PrimeTower.h"
#include "FanSpeedLayerTime.h"
#include "PrintFeature.h"
#include "LayerPlanBuffer.h"
namespace cura
{
/*!
* Secondary stage in Fused Filament Fabrication processing: The generated polygons are used in the gcode generation.
* Some polygons in the SliceDataStorage signify areas which are to be filled with parallel lines,
* while other polygons signify the contours which should be printed.
*
* The main function of this class is FffGcodeWriter::writeGCode().
*/
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.
/*!
* The number of the current meshgroup being processed.
*
* Used for sequential printing of objects.
* The first meshgroup will get number 1.
*/
int meshgroup_number;
/*
* Buffer for all layer plans (of type GCodePlanner)
*
* 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.
*/
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];
FanSpeedLayerTimeSettings fan_speed_layer_time_settings; //!< The settings used relating to minimal layer time and fan speeds.
Point last_position_planned; //!< The position of the head before planning the next layer
int current_extruder_planned; //!< The extruder train in use before planning the next layer
public:
FffGcodeWriter(SettingsBase* settings_)
: SettingsMessenger(settings_)
, layer_plan_buffer(this, gcode)
, last_position_planned(no_point)
, current_extruder_planned(0) // TODO: make configurable
{
meshgroup_number = 1;
max_object_height = 0;
}
/*!
* Reset the meshgroup number to process the next slicing.
*/
void resetMeshGroupNumber()
{
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);
if (output_file.is_open())
{
gcode.setOutputStream(&output_file);
return true;
}
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)
{
return gcode.getTotalFilamentUsed(extruder_nr);
}
/*!
* 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
*/
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
*/
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.
*/
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.
*/
void processNextMeshGroupCode(SliceDataStorage& storage);
/*!
* Add raft layer plans onto the FffGcodeWriter::layer_plan_buffer
*
* \param[in] storage 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.
* \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.
*/
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.
* \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.
* \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.
* \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 processDraftShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
/*!
* Calculate in which order to print the meshes.
*
* \param[in] storage where the slice data is stored.
* \param current_extruder The current extruder with which we last printed
* \return A vector of mesh indices ordered on print order.
*/
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.
* \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 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.
* \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 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.
* \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 addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcodeLayer, int layer_nr);
/*!
* Add thicker (multiple layers) sparse infill for a given part in a layer plan.
*
* \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 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 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);
/*!
* 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 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 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);
/*!
* 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 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
*/
void processInsets(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type);
/*!
* 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 part The part for which to create gcode
* \param layer_nr The current layer number.
* \param infill_overlap The distance by which the infill overlaps with the wall insets.
* \param fillAngle The angle in the XY plane at which the infill is generated.
* \param extrusionWidth extrusionWidth
*/
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int infill_overlap, int infill_angle, int extrusion_width);
/*!
* Add the support to the layer plan \p gcodeLayer of the current layer.
* \param[in] storage where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
* \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.
*/
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.
* \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);
/*!
* Add the support roofs to the layer plan \p gcodeLayer of the current layer.
* \param[in] storage where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
*/
void addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
/*!
* Change to a new extruder, and add the prime tower instructions if the new extruder is different from the last.
*
* 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 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
*/
void setExtruder_addPrime(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr);
/*!
* Add the prime tower gcode for the current layer.
* \param[in] storage where the slice data is stored.
* \param gcodeLayer The initial planning of the gcode of the layer.
* \param layer_nr The index of the layer to write the gcode of.
* \param prev_extruder The current extruder with which we last printed.
*/
void addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int prev_extruder);
/*!
* Add the end gcode and set all temperatures to zero.
*/
void finalize();
};
}//namespace cura
#endif // GCODE_WRITER_H
-474
Ver Arquivo
@@ -1,474 +0,0 @@
#include "FffPolygonGenerator.h"
#include <algorithm>
#include "slicer/Slicer.h"
#include "utils/gettime.h"
#include "utils/logoutput.h"
#include "MeshGroup.h"
#include "support.h"
#include "slicer/MultiVolumes.h"
#include "slicer/LayerPart.h"
#include "TextureProcessor.h"
#include "inset.h"
#include "skirt.h"
#include "skin.h"
#include "infill.h"
#include "raft.h"
#include "debug.h"
#include "Progress.h"
#include "PrintFeature.h"
namespace cura
{
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
{
if (CommandSocket::isInstantiated())
CommandSocket::getInstance()->beginSendSlicedObject();
if (!sliceModel(meshgroup, timeKeeper, storage))
{
return false;
}
slices2polygons(storage, timeKeeper);
return true;
}
bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeeper, SliceDataStorage& storage) /// slices the model
{
Progress::messageProgressStage(Progress::Stage::SLICING, &timeKeeper);
storage.model_min = meshgroup->min();
storage.model_max = meshgroup->max();
storage.model_size = storage.model_max - storage.model_min;
log("Slicing model...\n");
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.",initial_layer_thickness);
return false;
}
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.",layer_thickness);
return false;
}
if (meshgroup->getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
{
initial_layer_thickness = layer_thickness;
}
int initial_slice_z = initial_layer_thickness - layer_thickness / 2;
int layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1;
if(layer_count <= 0) //Model is shallower than layer_height_0, so not even the first layer is sliced. Return an empty model then.
{
Progress::messageProgressStage(Progress::Stage::INSET,&timeKeeper); //Continue directly with the inset stage, which will also immediately stop.
return true; //This is NOT an error state!
}
std::vector<Slicer*> slicerList;
for(unsigned int mesh_idx = 0; mesh_idx < meshgroup->meshes.size(); mesh_idx++)
{
Mesh& mesh = *meshgroup->meshes[mesh_idx];
Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
slicerList.push_back(slicer);
/*
for(SlicerLayer& layer : slicer->layers)
{
//Reporting the outline here slows down the engine quite a bit, so only do so when debugging.
sendPolygons("outline", layer_nr, layer.z, layer.polygonList);
sendPolygons("openoutline", layer_nr, layer.openPolygonList);
}
*/
Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size());
}
log("Layer count: %i\n", layer_count);
meshgroup->clear();///Clear the mesh face and vertex data, it is no longer needed after this point, and it saves a lot of memory.
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
//carveMultipleVolumes(storage.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++)
{
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 = 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 +=
meshStorage.getSettingInMicrons("layer_height_0")
- initial_slice_z;
if (has_raft)
{
layer.printZ +=
meshStorage.getSettingInMicrons("raft_base_thickness")
+ meshStorage.getSettingInMicrons("raft_interface_thickness")
+ meshStorage.getSettingAsCount("raft_surface_layers") * getSettingInMicrons("raft_surface_thickness")
+ meshStorage.getSettingInMicrons("raft_airgap");
}
if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) )
{
meshStorage.layer_nr_max_filled_layer = layer_nr; // last set by the highest non-empty layer
}
if (CommandSocket::isInstantiated())
{
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)
{
size_t total_layers = 0;
for (SliceMeshStorage& mesh : storage.meshes)
{
total_layers = std::max<unsigned int>(total_layers, mesh.layers.size());
}
//layerparts2HTML(storage, "output/output.html");
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
{
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);
/*
if (storage.support.generated)
{
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
{
Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Infill, layer_idx, support, 100); //getSettingInMicrons("support_line_width"));
}
}
}
*/
Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper);
int mesh_max_bottom_layer_count = 0;
if (getSettingBoolean("magic_spiralize"))
{
for(SliceMeshStorage& mesh : storage.meshes)
{
mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
}
}
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
{
if (!getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
{
processSkinsAndInfill(storage, layer_number);
}
Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers);
}
for(SliceMeshStorage& mesh : storage.meshes)
{
unsigned int combined_infill_layers = mesh.getSettingInMicrons("infill_sparse_thickness") / std::max(mesh.getSettingInMicrons("layer_height"), 1); //How many infill layers to combine to obtain the requested sparse thickness.
combineInfillLayers(mesh,combined_infill_layers);
}
storage.primeTower.computePrimeTowerMax(storage);
storage.primeTower.generatePaths(storage, total_layers);
processOozeShield(storage, total_layers);
processDraftShield(storage, total_layers);
processPlatformAdhesion(storage);
for(SliceMeshStorage& mesh : storage.meshes)
{
if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
{
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("wall_0_inset"), line_width_0, line_width_x, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
}
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
for (PolygonRef polyline : layer->openPolyLines)
{
Polygons segments;
for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
{
PolygonRef segment = segments.newPoly();
segment.add(polyline[point_idx-1]);
segment.add(polyline[point_idx]);
}
}
}
}
}
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int total_layers)
{
int n_empty_first_layers = 0;
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
{
bool layer_is_empty = true;
for (SliceMeshStorage& mesh : storage.meshes)
{
SliceLayer& layer = mesh.layers[layer_idx];
if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) )
{
layer_is_empty = false;
break;
}
}
if (layer_is_empty)
{
n_empty_first_layers++;
} else
{
break;
}
}
if (n_empty_first_layers > 0)
{
log("Removing %d layers because they are empty\n", n_empty_first_layers);
for (SliceMeshStorage& mesh : storage.meshes)
{
std::vector<SliceLayer>& layers = mesh.layers;
layers.erase(layers.begin(), layers.begin() + n_empty_first_layers);
for (SliceLayer& layer : layers)
{
layer.printZ -= n_empty_first_layers * layer_height;
}
}
total_layers -= n_empty_first_layers;
}
}
void FffPolygonGenerator::processSkinsAndInfill(SliceDataStorage& storage, unsigned int layer_nr)
{
for(SliceMeshStorage& mesh : storage.meshes)
{
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; }
int wall_line_count = mesh.getSettingAsCount("wall_line_count");
int skin_extrusion_width = mesh.getSettingInMicrons("skin_line_width");
int innermost_wall_extrusion_width = (wall_line_count == 1)? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x");
generateSkins(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_extrusion_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"), mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
if (mesh.getSettingInMicrons("infill_line_distance") > 0)
{
int infill_skin_overlap = 0;
bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") + 10;
if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
{
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);
}
}
}
}
void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage, unsigned int total_layers)
{
if (!getSettingBoolean("ooze_shield_enabled"))
{
return;
}
int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist");
for(unsigned int layer_nr=0; layer_nr<total_layers; layer_nr++)
{
storage.oozeShield.push_back(storage.getLayerOutlines(layer_nr, true).offset(ooze_shield_dist));
}
int largest_printed_radius = MM2INT(1.0); // TODO: make var a parameter, and perhaps even a setting?
for(unsigned int layer_nr=0; layer_nr<total_layers; layer_nr++)
{
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].offset(-largest_printed_radius).offset(largest_printed_radius);
}
int allowed_angle_offset = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height");//Allow for a 60deg angle in the oozeShield.
for(unsigned int layer_nr=1; layer_nr<total_layers; layer_nr++)
{
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr-1].offset(-allowed_angle_offset));
}
for(unsigned int layer_nr=total_layers-1; layer_nr>0; layer_nr--)
{
storage.oozeShield[layer_nr-1] = storage.oozeShield[layer_nr-1].unionPolygons(storage.oozeShield[layer_nr].offset(-allowed_angle_offset));
}
}
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned int total_layers)
{
int draft_shield_height = getSettingInMicrons("draft_shield_height");
int draft_shield_dist = getSettingInMicrons("draft_shield_dist");
int layer_height_0 = getSettingInMicrons("layer_height_0");
int layer_height = getSettingInMicrons("layer_height");
if (draft_shield_height < layer_height_0)
{
return;
}
unsigned int max_screen_layer = (draft_shield_height - layer_height_0) / layer_height + 1;
int layer_skip = 500 / layer_height + 1;
Polygons& draft_shield = storage.draft_protection_shield;
for (unsigned int layer_nr = 0; layer_nr < total_layers && layer_nr < max_screen_layer; layer_nr += layer_skip)
{
draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true));
}
storage.draft_protection_shield = draft_shield.convexHull(draft_shield_dist);
}
void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
{
switch(getSettingAsPlatformAdhesion("adhesion_type"))
{
case EPlatformAdhesion::SKIRT:
if (getSettingInMicrons("draft_shield_height") == 0)
{ // draft screen replaces skirt
generateSkirt(storage, getSettingInMicrons("skirt_gap"), getSettingAsCount("skirt_line_count"), getSettingInMicrons("skirt_minimal_length"));
}
break;
case EPlatformAdhesion::BRIM:
generateSkirt(storage, 0, getSettingAsCount("brim_line_count"), getSettingInMicrons("skirt_minimal_length"));
break;
case EPlatformAdhesion::RAFT:
generateRaft(storage, getSettingInMicrons("raft_margin"));
break;
}
Polygons skirt_sent = storage.skirt[0];
for (int extruder = 1; extruder < storage.meshgroup->getExtruderCount(); extruder++)
skirt_sent.add(storage.skirt[extruder]);
}
void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
{
if (mesh.getSettingAsCount("wall_line_count") == 0)
{
return;
}
int64_t fuzziness = mesh.getSettingInMicrons("magic_fuzzy_skin_thickness");
int64_t avg_dist_between_points = mesh.getSettingInMicrons("magic_fuzzy_skin_point_dist");
int64_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
int64_t range_random_point_dist = avg_dist_between_points / 2;
for (unsigned int layer_nr = 0; layer_nr < mesh.layers.size(); layer_nr++)
{
SliceLayer& layer = mesh.layers[layer_nr];
for (SliceLayerPart& part : layer.parts)
{
Polygons results;
Polygons& skin = (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0];
for (PolygonRef poly : skin)
{
// generate points in between p0 and p1
PolygonRef result = results.newPoly();
int64_t dist_left_over = rand() % (min_dist_between_points / 2); // the distance to be traversed on the line before making the first new point
Point* p0 = &poly.back();
for (Point& p1 : poly)
{ // 'a' is the (next) new point between p0 and p1
Point p0p1 = p1 - *p0;
int64_t p0p1_size = vSize(p0p1);
int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
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 fuzz = normal(perp_to_p0p1, r);
Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz;
result.add(pa);
dist_last_point = p0pa_dist;
}
dist_left_over = p0p1_size - dist_last_point;
p0 = &p1;
}
while (result.size() < 3 )
{
unsigned int point_idx = poly.size() - 2;
result.add(poly[point_idx]);
if (point_idx == 0) { break; }
point_idx--;
}
if (result.size() < 3)
{
result.clear();
for (Point& p : poly)
result.add(p);
}
}
skin = results;
}
}
}
}//namespace cura
-121
Ver Arquivo
@@ -1,121 +0,0 @@
#ifndef FFF_AREA_GENERATOR_H
#define FFF_AREA_GENERATOR_H
#include "MeshGroup.h"
#include "utils/polygonUtils.h"
#include "utils/NoCopy.h"
#include "utils/gettime.h"
#include "settings.h"
#include "sliceDataStorage.h"
#include "commandSocket.h"
#include "PrintFeature.h"
namespace cura
{
/*!
* Primary stage in Fused Filament Fabrication processing: Polygons are generated.
* The model is sliced and each slice consists of polygons representing the outlines: the boundaries between inside and outside the object.
* After slicing, the layers are processed; for example the wall insets are generated, and the areas which are to be filled with support and infill, which are all represented by polygons.
* In this stage nothing other than areas and circular paths are generated, which are both represented by polygons.
* No infill lines or support pattern etc. is generated.
*
* The main function of this class is FffPolygonGenerator::generateAreas().
*/
class FffPolygonGenerator : public SettingsMessenger, NoCopy
{
public:
/*!
* Basic constructor
*/
FffPolygonGenerator(SettingsBase* settings_)
: SettingsMessenger(settings_)
{
}
/*!
* Slice the \p object, process the outline information into inset perimeter polygons, support area polygons, etc.
*
* \param object The object to slice.
* \param timeKeeper Object which keeps track of timings of each stage.
* \param storage Output parameter: where the outlines are stored. See SliceLayerPart::outline.
*/
bool generateAreas(SliceDataStorage& storage, MeshGroup* object, TimeKeeper& timeKeeper);
private:
/*!
* Slice the \p object and store the outlines in the \p storage.
*
* \param object The object to slice.
* \param timeKeeper Object which keeps track of timings of each stage.
* \param storage Output parameter: where the outlines are stored. See SliceLayerPart::outline.
*
* \return Whether the process succeeded (always true).
*/
bool sliceModel(MeshGroup* object, TimeKeeper& timeKeeper, SliceDataStorage& storage); /// slices the model
/*!
* Processes the outline information as stored in the \p storage: generates inset perimeter polygons, support area polygons, etc.
*
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param timeKeeper Object which keeps track of timings of each stage.
*/
void slices2polygons(SliceDataStorage& storage, TimeKeeper& timeKeeper);
/*!
* Remove all bottom layers which are empty.
* \param storage Input and Ouput parameter: stores all layers
* \param layer_height The height of each layer
* \param total_layers The total number of layers
*/
void removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int total_layers);
/*!
* Generate the inset polygons which form the walls.
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param layer_nr The layer for which to generate the insets.
*/
void processInsets(SliceDataStorage& storage, unsigned int layer_nr);
/*!
* Generate the outline of the ooze shield.
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param total_layers The total number of layers
*/
void processOozeShield(SliceDataStorage& storage, unsigned int total_layers);
/*!
* Generate the skin areas.
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param layer_nr The layer for which to generate the skin areas.
*/
void processSkinsAndInfill(SliceDataStorage& storage, unsigned int layer_nr);
/*!
* Generate the polygons where the draft screen should be.
*
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param total_layers The total number of layers
*/
void processDraftShield(SliceDataStorage& storage, unsigned int total_layers);
/*!
* Generate the skirt/brim/raft areas/insets.
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
*/
void processPlatformAdhesion(SliceDataStorage& storage);
/*!
* Make the outer wall 'fuzzy'
*/
void processFuzzyWalls(SliceMeshStorage& mesh);
};
}//namespace cura
#endif // FFF_AREA_GENERATOR_H
-115
Ver Arquivo
@@ -1,115 +0,0 @@
#include "FffProcessor.h"
namespace cura
{
FffProcessor FffProcessor::instance; // definition must be in cpp
std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_meshgroup)
{
std::stringstream sstream;
if (first_meshgroup)
{
sstream << getAllLocalSettingsString(); // global settings
sstream << " -g";
}
else
{
sstream << " --next";
}
sstream << meshgroup.getAllLocalSettingsString();
for (int extruder_nr = 0; extruder_nr < meshgroup.getExtruderCount(); extruder_nr++)
{
ExtruderTrain* train = meshgroup.getExtruderTrain(extruder_nr);
sstream << " -e" << extruder_nr << train->getAllLocalSettingsString();
}
for (unsigned int mesh_idx = 0; mesh_idx < meshgroup.meshes.size(); mesh_idx++)
{
Mesh& mesh = *meshgroup.meshes[mesh_idx];
sstream << " -e" << mesh.getSettingAsCount("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
}
sstream << "\n";
return sstream.str();
}
bool FffProcessor::processFiles(const std::vector< std::string >& files)
{
time_keeper.restart();
MeshGroup* meshgroup = new MeshGroup(this);
for(std::string filename : files)
{
log("Loading %s from disk...\n", filename.c_str());
FMatrix3x3 matrix;
if (!loadMeshIntoMeshGroup(meshgroup, filename.c_str(), matrix))
{
logError("Failed to load model: %s\n", filename.c_str());
return false;
}
}
meshgroup->finalize();
log("Loaded from disk in %5.3fs\n", time_keeper.restart());
return processMeshGroup(meshgroup);
}
bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
{
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, first_meshgroup).c_str()); }
time_keeper.restart();
if (!meshgroup)
return false;
TimeKeeper time_keeper_total;
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, first_meshgroup);
return true;
}
if (meshgroup->getSettingBoolean("wireframe_enabled"))
{
log("starting Neith Weaver...\n");
Weaver w(this);
w.weave(meshgroup);
log("starting Neith Gcode generation...\n");
Wireframe2gcode gcoder(w, gcode_writer.gcode, this);
gcoder.writeGCode();
log("finished Neith Gcode generation...\n");
} else
{
SliceDataStorage storage(meshgroup);
if (!polygon_generator.generateAreas(storage, meshgroup, time_keeper))
{
return false;
}
Progress::messageProgressStage(Progress::Stage::EXPORT, &time_keeper);
gcode_writer.writeGCode(storage, time_keeper);
}
Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
CommandSocket::getInstance()->endSendSlicedObject();
}
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
first_meshgroup = false;
return true;
}
} // namespace cura
-173
Ver Arquivo
@@ -1,173 +0,0 @@
#ifndef FFF_PROCESSOR_H
#define FFF_PROCESSOR_H
#include "settings.h"
#include "FffGcodeWriter.h"
#include "FffPolygonGenerator.h"
#include "commandSocket.h"
#include "Weaver.h"
#include "Wireframe2gcode.h"
#include "Progress.h"
#include "utils/gettime.h"
#include "utils/NoCopy.h"
#define SHOW_ALL_SETTINGS true
namespace cura {
//FusedFilamentFabrication processor. Singleton class
class FffProcessor : public SettingsBase , NoCopy
{
private:
/*!
* The FffProcessor used for the (current) slicing (The instance of this singleton)
*/
static FffProcessor instance;
FffProcessor()
: polygon_generator(this)
, gcode_writer(this)
, first_meshgroup(true)
{
}
public:
/*!
* Get the instance
* \return The instance
*/
static FffProcessor* getInstance()
{
return &instance;
}
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;
/*!
* Whether the firs meshgroup is being processed.
*/
bool first_meshgroup;
/*!
* 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.
*/
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()
{
gcode_writer.resetMeshGroupNumber();
}
/*!
* 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)
{
return gcode_writer.getTotalFilamentUsed(extruder_nr);
}
/*!
* 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);
};
}//namespace cura
#endif//FFF_PROCESSOR_H
-77
Ver Arquivo
@@ -1,77 +0,0 @@
#ifndef FLOW_TEMP_GRAPH
#define FLOW_TEMP_GRAPH
#include <cassert>
#include "utils/logoutput.h"
namespace cura
{
/*!
* Class representing a graph matching a flow to a temperature.
* The graph generally consists of several linear line segments between points at which the temperature and flow are matched.
*/
class FlowTempGraph
{
public:
struct Datum
{
double flow; //!< The flow in mm^3/s
double temp; //!< The temperature in *C
Datum(double flow, double temp)
: flow(flow)
, temp(temp)
{}
};
std::vector<Datum> data; //!< The points of the graph between which the graph is linearly interpolated
FlowTempGraph()
{}
/*!
* Get the temperature corresponding to a specific flow.
*
* For flows outside of the chart, the temperature at the minimal or maximal flow is returned.
* When the graph is empty, the @p material_print_temperature is returned.
*
* \param flow the flow in mm^3/s
* \param material_print_temperature The default printing temp (backward compatibility for when the graph fails)
* \return the corresponding temp
*/
double getTemp(double flow, double material_print_temperature, bool flow_dependent_temperature)
{
if (!flow_dependent_temperature || data.size() == 0)
{
return material_print_temperature;
}
if (data.size() == 1)
{
return data.front().temp;
}
if (flow < data.front().flow)
{
logWarning("Warning! Flow too low!\n"); // TODO
return data.front().temp;
}
Datum* last_datum = &data.front();
for (unsigned int datum_idx = 1; datum_idx < data.size(); datum_idx++)
{
Datum& datum = data[datum_idx];
if (datum.flow >= flow)
{
return last_datum->temp + (datum.temp - last_datum->temp) * (flow - last_datum->flow) / (datum.flow - last_datum->flow);
}
last_datum = &datum;
}
logWarning("Warning! Flow too high!\n"); // TODO
return data.back().temp;
};
};
} // namespace cura
#endif // FLOW_TEMP_GRAPH
-225
Ver Arquivo
@@ -1,225 +0,0 @@
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
#include "LayerPlanBuffer.h"
#include "gcodeExport.h"
#include "utils/logoutput.h"
namespace cura {
void LayerPlanBuffer::flush()
{
if (buffer.size() > 0)
{
insertPreheatCommands(); // insert preheat commands of the very last layer
}
while (!buffer.empty())
{
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
}
buffer.pop_front();
}
}
void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp)
{
double acc_time = 0.0;
for (unsigned int path_idx = 0; path_idx < extruder_plan_before.paths.size(); path_idx++)
{
GCodePath& path = extruder_plan_before.paths[path_idx];
acc_time += path.estimates.getTotalTime();
if (acc_time > time_after_extruder_plan_start)
{
// 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(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
}
double LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int 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 layer_idx = layer_plan_idx; int(layer_idx) >= 0; layer_idx--)
{
GCodePlanner& layer = *layers[layer_idx];
if (!first_it)
{
extruder_plan_before_idx = layer.extruder_plans.size() - 1;
}
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.
return preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(preheat_config.getStandbyTemp(extruder), extruder, required_temp, false);
}
void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp)
{
// time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
double time_before_extruder_plan_end = 0.5 * preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(prev_extruder_plan.required_temp, extruder, required_temp, true);
double time_after_extruder_plan_start = prev_extruder_plan.estimates.getTotalTime() - time_before_extruder_plan_end;
if (time_after_extruder_plan_start < 0)
{
time_after_extruder_plan_start = 0; // don't override the extruder plan with same extruder of the previous layer
}
insertPreheatCommand(prev_extruder_plan, time_after_extruder_plan_start, extruder, required_temp);
}
void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int 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;
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--)
{
GCodePlanner& layer = *layers[layer_idx];
if (!first_it)
{
extruder_plan_before_idx = layer.extruder_plans.size() - 1;
}
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
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<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int 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 = 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
prev_extruder_plan->insertCommand(prev_extruder_plan->paths.size(), prev_extruder, preheat_config.getStandbyTemp(prev_extruder), false);
}
if (prev_extruder == extruder)
{
if (preheat_config.usesFlowDependentTemp(extruder))
{
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, required_temp);
}
}
else
{
insertPreheatCommand_multiExtrusion(layers, layer_plan_idx, extruder_plan_idx);
}
}
void LayerPlanBuffer::insertPreheatCommands()
{
if (buffer.back().extruder_plans.size() == 0 || (buffer.back().extruder_plans.size() == 1 && buffer.back().extruder_plans[0].paths.size() == 0))
{ // disregard empty layer
buffer.pop_back();
return;
}
std::vector<GCodePlanner*> layers;
layers.reserve(buffer.size());
for (GCodePlanner& layer_plan : buffer)
{
layers.push_back(&layer_plan);
}
unsigned int layer_idx = layers.size() - 1;
// insert commands for all extruder plans on this layer
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];
double time = extruder_plan.estimates.getTotalUnretractedTime();
if (time <= 0.0
|| extruder_plan.estimates.getMaterial() == 0.0 // extruder plan only consists of moves (when an extruder switch occurs at the beginning of a layer)
)
{
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);
insertPreheatCommand(layers, layer_idx, extruder_plan_idx);
}
}
} // namespace cura
-133
Ver Arquivo
@@ -1,133 +0,0 @@
#ifndef LAYER_PLAN_BUFFER_H
#define LAYER_PLAN_BUFFER_H
#include <list>
#include "settings.h"
#include "commandSocket.h"
#include "gcodeExport.h"
#include "gcodePlanner.h"
#include "MeshGroup.h"
#include "Preheat.h"
namespace cura
{
class LayerPlanBuffer : SettingsMessenger
{
GCodeExport& gcode;
Preheat preheat_config; //!< the nozzle and material temperature settings for each extruder train.
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.
public:
std::list<GCodePlanner> buffer; //!< The buffer containing several layer plans (GCodePlanner) before writing them to gcode.
LayerPlanBuffer(SettingsBaseVirtual* settings, GCodeExport& gcode)
: SettingsMessenger(settings)
, gcode(gcode)
{ }
void setPreheatConfig(MeshGroup& settings)
{
preheat_config.setConfig(settings);
}
/*!
* Place a new layer plan (GcodePlanner) by constructing it with the given arguments.
* Pop back the oldest layer plan is it exceeds the buffer size and write it to gcode.
*/
template<typename... Args>
GCodePlanner& emplace_back(Args&&... constructor_args)
{
if (buffer.size() > 0)
{
insertPreheatCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
}
buffer.emplace_back(constructor_args...);
if (buffer.size() > buffer_size)
{
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
}
buffer.pop_front();
}
return buffer.back();
}
/*!
* Write all remaining layer plans (GCodePlanner) to gcode and empty the buffer.
*/
void flush();
/*!
* 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_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_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 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
*/
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),
* preheat the extruder to the temperature corresponding to the average flow of the second extruder plan.
*
* The preheat commands are inserted such that the middle of the temperature change coincides with the start of the next layer.
*
* \param prev_extruder_plan The former extruder plan (of the former layer)
* \param extruder The extruder for which too set the temperature
* \param required_temp The required temperature for the second extruder plan
*/
void insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp);
/*!
* Insert the preheat command for an extruder plan which is preceded by an extruder plan with a different extruder.
* Find the time window in which this extruder hasn't been used
* and compute at what time the preheat command needs to be inserted.
* Then insert the preheat command in the right extruder plan.
*
* \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<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 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<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();
};
} // namespace cura
#endif // LAYER_PLAN_BUFFER_H
-27
Ver Arquivo
@@ -1,27 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#ifndef MAT_COORD_H
#define MAT_COORD_H
#include "utils/FPoint.h"
namespace cura
{
/*!
* Coordinates in a specific texture bitmap
*/
struct MatCoord
{
FPoint coords;
int mat_id; //!< Material id
MatCoord() //!< non-initializing constructor
{}
MatCoord(FPoint coords, int mat_id) //!< constructor
: coords(coords)
, mat_id(mat_id)
{}
};
} // namespace cura
#endif // MAT_COORD_H
-27
Ver Arquivo
@@ -1,27 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#ifndef MAT_SEGMENT_H
#define MAT_SEGMENT_H
#include "MatCoord.h"
namespace cura
{
/*!
* Coordinates in a specific texture bitmap
*/
struct MatSegment
{
MatCoord start;
MatCoord end;
MatSegment() //!< non-initializing constructor
{}
MatSegment(MatCoord start, MatCoord end)
: start(start)
, end(end)
{}
};
} // namespace cura
#endif // MAT_SEGMENT_H
-33
Ver Arquivo
@@ -1,33 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#include <limits> // numeric limits
#include <algorithm> // min max
#include <iostream>
#include "Material.h"
namespace cura
{
void Material::setData(unsigned char* data)
{
this->data = data;
}
void Material::setWidthHeight(int width, int height)
{
this->width = width;
this->height = height;
}
float Material::getColor(float x, float y)
{
int w_idx = std::max(0, std::min(int (x * width), width - 1));
int h_idx = std::max(0, std::min(int (y * height), height - 1));
unsigned char r = data[(h_idx * width + w_idx) * 3];
return (float) r / std::numeric_limits<unsigned char>::max();
}
} // namespace cura
-30
Ver Arquivo
@@ -1,30 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#ifndef MATERIAL_H
#define MATERIAL_H
namespace cura
{
class Material
{
public:
void setData(unsigned char* data);
void setWidthHeight(int width, int height);
/*!
* get some value representing the getColor
*
* red?
*
* TODO
*
* \return a value between zero and one
*/
float getColor(float x, float y);
protected:
unsigned char* data; //!< pixel data in rgb-row-first (or bgr-row first ?)
int width, height; //!< image dimensions
};
} // namespace cura
#endif // MATERIAL_H
-42
Ver Arquivo
@@ -1,42 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#include "MaterialBase.h"
namespace cura
{
Material* MaterialBase::add(std::string name)
{
name_to_mat_idx[name] = materials.size();
materials.emplace_back();
return &materials.back();
}
Material* MaterialBase::getMat(unsigned int id)
{
if (id < materials.size())
{
return &materials[id];
}
else
{
return nullptr;
}
}
int MaterialBase::getMatId(std::string name)
{
auto it = name_to_mat_idx.find(name);
if (it == name_to_mat_idx.end())
{
return -1;
}
else
{
return it->second;
}
}
} // namespace cura
-27
Ver Arquivo
@@ -1,27 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#ifndef MATERIAL_BASE_H
#define MATERIAL_BASE_H
#include <unordered_map>
#include <string>
#include <vector>
#include "Material.h"
namespace cura
{
class MaterialBase
{
public:
int getMatId(std::string name);
Material* add(std::string name);
Material* getMat(unsigned int id);
protected:
std::unordered_map<std::string, int> name_to_mat_idx;
std::vector<Material> materials;
};
} // namespace cura
#endif // MATERIAL_BASE_H
-230
Ver Arquivo
@@ -1,230 +0,0 @@
#include "MergeInfillLines.h"
#include <algorithm> // min
#include "utils/linearAlg2D.h"
namespace cura
{
void MergeInfillLines::writeCompensatedMove(Point& to, double speed, GCodePath& last_path, int64_t new_line_width)
{
double old_line_width = INT2MM(last_path.config->getLineWidth());
double new_line_width_mm = INT2MM(new_line_width);
double speed_mod = old_line_width / new_line_width_mm;
double extrusion_mod = new_line_width_mm / old_line_width;
double new_speed = std::min(speed * speed_mod, 150.0); // TODO: hardcoded value: max extrusion speed is 150 mm/s = 9000 mm/min
sendPolygon(last_path.config->type, gcode.getPositionXY(), to, last_path.getLineWidth());
gcode.writeMove(to, new_speed, last_path.getExtrusionMM3perMM() * extrusion_mod);
}
bool MergeInfillLines::mergeInfillLines(double speed, unsigned int& path_idx)
{ //Check for lots of small moves and combine them into one large line
Point prev_middle;
Point last_middle;
int64_t line_width;
if (isConvertible(path_idx, prev_middle, last_middle, line_width, false))
{
// path_idx + 3 is the index of the second extrusion move to be converted in combination with the first
{
GCodePath& move_path = paths[path_idx];
for(unsigned int point_idx = 0; point_idx < move_path.points.size() - 1; point_idx++)
{
gcode.writeMove(move_path.points[point_idx], speed, move_path.getExtrusionMM3perMM());
}
gcode.writeMove(prev_middle, travelConfig.getSpeed(), 0);
GCodePath& last_path = paths[path_idx + 3];
writeCompensatedMove(last_middle, speed, last_path, line_width);
}
path_idx += 2;
extruder_plan.handleInserts(path_idx, gcode);
for (; isConvertible(path_idx, prev_middle, last_middle, line_width, true); path_idx += 2)
{
extruder_plan.handleInserts(path_idx, gcode);
GCodePath& last_path = paths[path_idx + 3];
writeCompensatedMove(last_middle, speed, last_path, line_width);
}
path_idx = path_idx + 1; // means that the next path considered is the travel path after the converted extrusion path corresponding to the updated path_idx
extruder_plan.handleInserts(path_idx, gcode);
return true;
}
return false;
};
bool MergeInfillLines::isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first)
{
unsigned int idx = path_idx_first_move;
if (idx + 3 > paths.size()-1)
{
return false;
}
if ( paths[idx+0].config != &travelConfig // must be travel
|| paths[idx+1].points.size() > 1 // extrusion path is single line
|| paths[idx+1].config == &travelConfig // must be extrusion
// || paths[idx+2].points.size() > 1 // travel must be direct
|| paths[idx+2].config != &travelConfig // must be travel
|| paths[idx+3].points.size() > 1 // extrusion path is single line
|| paths[idx+3].config == &travelConfig // must be extrusion
|| paths[idx+1].config != paths[idx+3].config // both extrusion moves should have the same config
)
{
return false;
}
if (!(paths[idx+1].config->type == PrintFeatureType::Infill || paths[idx+1].config->type == PrintFeatureType::Skin))
{ // only (skin) infill lines can be merged (note that the second extrusion line config is already checked to be the same as the first in code above)
return false;
}
if (paths[idx+1].space_fill_type != SpaceFillType::Lines || paths[idx+3].space_fill_type != SpaceFillType::Lines)
{ // both extrusion moves must be of lines space filling type!
return false;
}
int64_t line_width = paths[idx+1].config->getLineWidth();
Point& a = paths[idx+0].points.back(); // first extruded line from
Point& b = paths[idx+1].points.back(); // first extruded line to
Point& c = paths[idx+2].points.back(); // second extruded line from
Point& d = paths[idx+3].points.back(); // second extruded line to
return isConvertible(a, b, c, d, line_width, first_middle, second_middle, resulting_line_width, use_second_middle_as_first);
}
bool MergeInfillLines::isConvertible(const Point& a, const Point& b, const Point& c, const Point& d, int64_t line_width, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first)
{
use_second_middle_as_first = false;
int64_t max_line_width = nozzle_size * 3 / 2;
Point ab = b - a;
Point cd = d - c;
if (b == c)
{
return false; // the line segments are connected!
}
int64_t ab_size = vSize(ab);
int64_t cd_size = vSize(cd);
if (ab_size > nozzle_size * 5 || cd_size > nozzle_size * 5)
{
return false; // infill lines are too long; otherwise infill lines might be merged when the next infill line is coincidentally shorter like |, would become \ ...
}
// if the lines are in the same direction then abs( dot(ab,cd) / |ab| / |cd| ) == 1
int64_t prod = dot(ab,cd);
if (std::abs(prod) + 400 < ab_size * cd_size) // 400 = 20*20, where 20 micron is the allowed inaccuracy in the dot product, introduced by the inaccurate point locations of a,b,c,d
{
return false; // extrusion moves not in the same or opposite diraction
}
// make lines in the same direction by flipping one
if (prod < 0)
{
ab = ab * -1;
}
else if (prod == 0)
{
return false; // lines are orthogonal!
}
else if (b == d || a == c)
{
return false; // the line segments are connected!
}
first_middle = (use_second_middle_as_first)?
second_middle :
(a + b) / 2;
second_middle = (c + d) / 2;
Point dir_vector_perp = turn90CCW(second_middle - first_middle);
int64_t dir_vector_perp_length = vSize(dir_vector_perp); // == dir_vector_length
if (dir_vector_perp_length == 0)
{
return false;
}
if (dir_vector_perp_length > 5 * nozzle_size)
{
return false; // infill lines too far apart
}
Point infill_vector = (cd + ab) / 2; // (similar to) average line / direction of the infill
// compute the resulting line width
resulting_line_width = std::abs( dot(dir_vector_perp, infill_vector) / dir_vector_perp_length );
if (resulting_line_width > max_line_width)
{
return false; // combined lines would be too wide
}
if (resulting_line_width == 0)
{
return false; // dot is zero, so lines are in each others extension, not next to eachother
}
// check whether two lines are adjacent (note: not 'line segments' but 'lines')
Point ac = c - first_middle;
Point infill_vector_perp = turn90CCW(infill_vector);
int64_t perp_proj = dot(ac, infill_vector_perp);
int64_t infill_vector_perp_length = vSize(infill_vector_perp);
if (std::abs(std::abs(perp_proj) / infill_vector_perp_length - line_width) > 20) // it should be the case that dot(ac, infill_vector_perp) / |infill_vector_perp| == line_width
{
return false; // lines are too far apart or too close together
}
// check whether the two line segments are adjacent.
// full infill in a narrow area might result in line segments with arbitrary distance between them
// the more the narrow passage in the area gets aligned with the infill direction, the further apart the line segments will be
// however, distant line segments might also be due to different narrow passages, so we limit the distance between merged line segments.
if (!LinearAlg2D::lineSegmentsAreCloserThan(a, b, c, d, line_width * 2))
{
return false;
}
return true;
};
/*
void MergeInfillLines::merge(Point& from, Point& p0, Point& p1)
{ //Check for lots of small moves and combine them into one large line
if (path->points.size() == 1 && path->config != &travelConfig); // && shorterThen(from - path->points[0], path->config->getLineWidth() * 2))
{
Point p0 = path->points[0];
unsigned int path_idx_last = path_idx + 1; // index of the last short move
while(path_idx_last < paths.size() && paths[path_idx_last].points.size() == 1 && shorterThen(p0 - paths[path_idx_last].points[0], path->config->getLineWidth() * 2))
{
p0 = paths[path_idx_last].points[0];
path_idx_last ++;
}
if (paths[path_idx_last-1].config == &travelConfig)
path_idx_last --;
if (path_idx_last > path_idx + 2)
{
p0 = from;
for(unsigned int path_idx_short = path_idx; path_idx_short < path_idx_last-1; path_idx_short+=2)
{
int64_t oldLen = vSize(p0 - paths[path_idx_short].points[0]);
Point newPoint = (paths[path_idx_short].points[0] + paths[path_idx_short+1].points[0]) / 2;
int64_t newLen = vSize(from - newPoint);
if (newLen > 0)
{
if (oldLen > 0)
gcode.writeMove(newPoint, speed * oldLen / newLen, path->getExtrusionMM3perMM() * newLen / oldLen);
else
gcode.writeMove(newPoint, speed, path->getExtrusionMM3perMM());
}
}
gcode.writeMove(paths[path_idx_last-1].points[0], speed, path->getExtrusionMM3perMM());
path_idx = path_idx_last - 1;
continue;
}
}
}*/
}//namespace cura
-99
Ver Arquivo
@@ -1,99 +0,0 @@
#ifndef MERGE_INFILL_LINES_H
#define MERGE_INFILL_LINES_H
#include "utils/intpoint.h"
#include "gcodeExport.h"
#include "gcodePlanner.h"
namespace cura
{
class MergeInfillLines
{
// void merge(Point& from, Point& p0, Point& p1);
GCodeExport& gcode; //!< Where to write the combined line to
int layer_nr; //!< The current layer number
std::vector<GCodePath>& paths; //!< The paths currently under consideration
ExtruderPlan& extruder_plan; //!< The extruder plan of the paths currently under consideration
GCodePathConfig& travelConfig; //!< The travel settings used to see whether a path is a travel path or an extrusion path
int64_t nozzle_size; //!< The diameter of the hole in the nozzle
/*!
* Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
* \param path_idx_first_move Index into MergeInfillLines::paths to the travel before the two extrusion moves udner consideration
* \param first_middle Output parameter: the middle of the first extrusion move
* \param second_middle Input/Output parameter: outputs the middle of the second extrusion move; inputs \p first_middle so we don't have to compute it
* \param resulting_line_width Output parameter: The width of the resulting combined line (the average length of the lines combined)
* \param use_second_middle_as_first Whether to use \p second_middle as input parameter for \p first_middle
* \return Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
*/
bool isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first = false);
/*!
* Whether the two consecutive extrusion paths (ab and cd) are convitrible to a single line segment.
*
* Note: as an optimization the \p second_middle from the previous call to isConvertible can be used for \p first_middle, instead of recomputing it.
*
* \param a first from
* \param b first to
* \param c second from
* \param d second to
* \param line_width The line width of the moves
* \param first_middle Output parameter: the middle of the first extrusion move
* \param second_middle Input/Output parameter: outputs the middle of the second extrusion move; inputs \p first_middle so we don't have to compute it
* \param resulting_line_width Output parameter: The width of the resulting combined line (the average length of the lines combined)
* \param use_second_middle_as_first Whether to use \p second_middle as input parameter for \p first_middle
* \return Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
*/
bool isConvertible(const Point& a, const Point& b, const Point& c, const Point& d, int64_t line_width, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first = false);
/*!
* Write an extrusion move with compensated width and compensated speed so that the material flow will be the same.
*
* \param to The point to move to
* \param speed The original speed
* \param old_path The original path
* \param new_line_width The width of the convewrted line (approximately the length of the original line)
*/
void writeCompensatedMove(Point& to, double speed, GCodePath& old_path, int64_t new_line_width);
public:
/*!
* Simple constructor only used by MergeInfillLines::isConvertible to easily convey the environment
*/
MergeInfillLines(GCodeExport& gcode, int layer_nr, std::vector<GCodePath>& paths, ExtruderPlan& extruder_plan, GCodePathConfig& travelConfig, int64_t nozzle_size)
: gcode(gcode), layer_nr(layer_nr), paths(paths), extruder_plan(extruder_plan), travelConfig(travelConfig), nozzle_size(nozzle_size) { }
/*!
* Check for lots of small moves and combine them into one large line.
* Updates \p path_idx to the next path which is not combined.
*
* \param gcode Where to write the combined line to
* \param paths The paths currently under consideration
* \param travelConfig The travel settings used to see whether a path is a travel path or an extrusion path
* \param nozzle_size The diameter of the hole in the nozzle
* \param speed A factor used to scale the movement speed
* \param path_idx Input/Output parameter: The current index in \p paths where to start combining and the current index after combining as output parameter.
* \return Whether lines have been merged and normal path-to-gcode generation can be skipped for the current resulting \p path_idx .
*/
bool mergeInfillLines(double speed, unsigned int& path_idx);
/*!
* send a polygon through the command socket from the previous point to the given point
*/
void sendPolygon(PrintFeatureType print_feature_type, Point from, Point to, int line_width)
{
if (CommandSocket::isInstantiated())
{
// we should send this travel as a non-retraction move
cura::Polygons pathPoly;
PolygonRef path = pathPoly.newPoly();
path.add(from);
path.add(to);
CommandSocket::getInstance()->sendPolygons(print_feature_type, layer_nr, pathPoly, line_width);
}
}
};
}//namespace cura
#endif // MERGE_INFILL_LINES_H
-365
Ver Arquivo
@@ -1,365 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include "MeshGroup.h"
#include "utils/logoutput.h"
#include "utils/string.h"
namespace cura
{
FILE* binaryMeshBlob = nullptr;
/* Custom fgets function to support Mac line-ends in Ascii STL files. OpenSCAD produces this when used on Mac */
void* fgets_(char* ptr, size_t len, FILE* f)
{
while(len && fread(ptr, 1, 1, f) > 0)
{
if (*ptr == '\n' || *ptr == '\r')
{
*ptr = '\0';
return ptr;
}
else if (*ptr =='\0')
{
return ptr;
}
ptr++;
len--;
}
return nullptr;
}
bool loadMeshSTL_ascii(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rt");
char buffer[1024];
FPoint3 vertex;
int n = 0;
Point3 v0(0,0,0), v1(0,0,0), v2(0,0,0);
while(fgets_(buffer, sizeof(buffer), f))
{
if (sscanf(buffer, " vertex %f %f %f", &vertex.x, &vertex.y, &vertex.z) == 3)
{
n++;
switch(n)
{
case 1:
v0 = matrix.apply(vertex);
break;
case 2:
v1 = matrix.apply(vertex);
break;
case 3:
v2 = matrix.apply(vertex);
mesh->addFace(v0, v1, v2);
n = 0;
break;
}
}
}
fclose(f);
mesh->finish();
return true;
}
bool loadMeshSTL_binary(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rb");
fseek(f, 0L, SEEK_END);
long long file_size = ftell(f); //The file size is the position of the cursor after seeking to the end.
rewind(f); //Seek back to start.
size_t face_count = (file_size - 80 - sizeof(uint32_t)) / 50; //Subtract the size of the header. Every face uses exactly 50 bytes.
char buffer[80];
//Skip the header
if (fread(buffer, 80, 1, f) != 1)
{
fclose(f);
return false;
}
uint32_t reported_face_count;
//Read the face count. We'll use it as a sort of redundancy code to check for file corruption.
if (fread(&reported_face_count, sizeof(uint32_t), 1, f) != 1)
{
fclose(f);
return false;
}
if (reported_face_count != face_count)
{
logWarning("Face count reported by file (%s) is not equal to actual face count (%s). File could be corrupt!\n", std::to_string(reported_face_count).c_str(), std::to_string(face_count).c_str());
}
//For each face read:
//float(x,y,z) = normal, float(X,Y,Z)*3 = vertexes, uint16_t = flags
// Every Face is 50 Bytes: Normal(3*float), Vertices(9*float), 2 Bytes Spacer
mesh->faces.reserve(face_count);
mesh->vertices.reserve(face_count);
for (unsigned int i = 0; i < face_count; i++)
{
if (fread(buffer, 50, 1, f) != 1)
{
fclose(f);
return false;
}
float *v= ((float*)buffer)+3;
Point3 v0 = matrix.apply(FPoint3(v[0], v[1], v[2]));
Point3 v1 = matrix.apply(FPoint3(v[3], v[4], v[5]));
Point3 v2 = matrix.apply(FPoint3(v[6], v[7], v[8]));
mesh->addFace(v0, v1, v2);
}
fclose(f);
mesh->finish();
return true;
}
bool loadMeshSTL(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "r");
if (f == nullptr)
{
return false;
}
//Skip any whitespace at the beginning of the file.
unsigned long long num_whitespace = 0; //Number of whitespace characters.
unsigned char whitespace;
if (fread(&whitespace, 1, 1, f) != 1)
{
fclose(f);
return false;
}
while(isspace(whitespace))
{
num_whitespace++;
if (fread(&whitespace, 1, 1, f) != 1)
{
fclose(f);
return false;
}
}
fseek(f, num_whitespace, SEEK_SET); //Seek to the place after all whitespace (we may have just read too far).
char buffer[6];
if (fread(buffer, 5, 1, f) != 1)
{
fclose(f);
return false;
}
fclose(f);
buffer[5] = '\0';
if (stringcasecompare(buffer, "solid") == 0)
{
bool load_success = loadMeshSTL_ascii(mesh, filename, matrix);
if (!load_success)
return false;
// This logic is used to handle the case where the file starts with
// "solid" but is a binary file.
if (mesh->faces.size() < 1)
{
mesh->clear();
return loadMeshSTL_binary(mesh, filename, matrix);
}
return true;
}
return loadMeshSTL_binary(mesh, filename, matrix);
}
void readBMP(Material* mat, const char* filename)
{
FILE* f = fopen(filename, "rb");
if (f == nullptr)
{
logError("ERROR: couldn't load image file %s.\n", filename);
return;
}
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
int width = *(int*)&info[18];
int height = *(int*)&info[22];
int size = 3 * width * height;
unsigned char* data = new unsigned char[size]; // allocate 3 bytes per pixel
fread(data, sizeof(unsigned char), size, f); // read the rest of the data at once
fclose(f);
// for (int i = 0; i < size; i += 3)
// {
// unsigned char tmp = data[i];
// data[i] = data[i+2];
// data[i+2] = tmp;
// } // BGR ==> RGB
mat->setData(data);
mat->setWidthHeight(width, height);
}
void loadMatImage(Material* mat, const char* filename)
{
const char* ext = strrchr(filename, '.');
if (ext && (strcmp(ext, ".bmp") == 0 || strcmp(ext, ".BMP") == 0))
{
readBMP(mat, filename);
}
else
{
logError("ERROR: trying to load unsupported image. File %s has %s extension.\n", filename, ext);
}
}
void loadMaterialBase(TexturedMesh* mesh, const char* filename)
{
FILE* f = fopen(filename, "rt");
if (f == nullptr)
{
logError("ERROR: Couldn't load MTL file %s.\n", filename);
return;
}
char buffer[1024];
char mat_name [100];
char mat_file [100];
char map_type [10];
Material* last_mat = nullptr;
while(fgets_(buffer, sizeof(buffer), f))
{
if (buffer[0] == '#')
{
continue;
}
if (sscanf(buffer, "map_%s %s", map_type, mat_file) == 2 // we don't care what type of map it specifies (currently)
|| sscanf(buffer, "bump %s", mat_file) == 1
|| sscanf(buffer, "disp %s", mat_file) == 1
|| sscanf(buffer, "decal %s", mat_file) == 1
|| sscanf(buffer, "refl %s", mat_file) == 1
)
{
std::string parent_dir = std::string(filename).substr(0, std::string(filename).find_last_of("/\\"));
std::string mtl_file = parent_dir + "/" + mat_file;
if (last_mat)
{
loadMatImage(last_mat, mtl_file.c_str());
}
}
else if (sscanf(buffer, "newmtl %s", mat_name) == 1)
{
last_mat = mesh->addMaterial(mat_name);
}
}
fclose(f);
}
bool loadMeshOBJ(TexturedMesh* mesh, const char* filename, const FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rt");
if (f == nullptr)
{
return false;
}
char buffer[1024];
FPoint3 vertex;
Point3 vertex_indices;
float texture_x;
float texture_y;
float temp;
char face_index_buffer_1 [100];
char face_index_buffer_2 [100];
char face_index_buffer_3 [100];
char str_buffer [100];
while(fgets_(buffer, sizeof(buffer), f))
{
if (buffer[0] == '#')
{
continue;
}
if (sscanf(buffer, "v %f %f %f", &vertex.x, &vertex.y, &vertex.z) == 3)
{
Point3 v = matrix.apply(vertex);
mesh->addVertex(v);
}
else if (sscanf(buffer, "vt %f %f", &texture_x, &texture_y) == 2)
{
mesh->addTextureCoord(texture_x, texture_y);
}
else if (sscanf(buffer, "f %s %s %s", face_index_buffer_1, face_index_buffer_2, face_index_buffer_3) == 3)
{
int normal_vector_index; // unused
Point3 texture_indices(0, 0, 0); // becomes -1 if no texture data supplied
int n_scanned_1 = sscanf(face_index_buffer_1, "%d/%d/%d", &vertex_indices.x, &texture_indices.x, &normal_vector_index);
int n_scanned_2 = sscanf(face_index_buffer_2, "%d/%d/%d", &vertex_indices.y, &texture_indices.y, &normal_vector_index);
int n_scanned_3 = sscanf(face_index_buffer_3, "%d/%d/%d", &vertex_indices.z, &texture_indices.z, &normal_vector_index);
if (n_scanned_1 > 0 && n_scanned_2 > 0 && n_scanned_3 > 0)
{
mesh->addFace(vertex_indices.x - 1, vertex_indices.y - 1, vertex_indices.z - 1, texture_indices.x - 1, texture_indices.y - 1, texture_indices.z - 1);
// obj files count vertex indices starting from 1!
}
}
else if (sscanf(buffer, "mtllib %s", str_buffer) == 1)
{
std::string parent_dir = std::string(filename).substr(0, std::string(filename).find_last_of("/\\"));
std::string mtl_file = parent_dir + "/" + str_buffer;
loadMaterialBase(mesh, mtl_file.c_str());
}
else if (sscanf(buffer, "usemtl %s", str_buffer) == 1)
{
mesh->setMaterial(str_buffer);
}
else if (sscanf(buffer, "vn %f %f %f", &temp, &temp, &temp) == 3)
{
// do nothing
}
else if (buffer[0] == '\0')
{
// empty line, do nothing
}
else
{
logError("Cannot parse line \"%s\"\n", buffer);
}
}
fclose(f);
mesh->finish();
return true;
}
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
{
const char* ext = strrchr(filename, '.');
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
{
Mesh* mesh = new Mesh(object_parent_settings ? object_parent_settings : meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
if (loadMeshSTL(mesh,filename,transformation)) //Load it! If successful...
{
meshgroup->meshes.push_back(mesh);
return true;
}
else
{
delete mesh;
}
}
else if (ext && (strcmp(ext, ".obj") == 0 || strcmp(ext, ".OBJ") == 0))
{
TexturedMesh* mesh = new TexturedMesh(object_parent_settings ? object_parent_settings : meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
if (loadMeshOBJ(mesh,filename,transformation)) //Load it! If successful...
{
meshgroup->meshes.push_back(mesh);
return true;
}
else
{
delete mesh;
}
}
return false;
}
}//namespace cura
-152
Ver Arquivo
@@ -1,152 +0,0 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef MESH_GROUP_H
#define MESH_GROUP_H
#include "utils/NoCopy.h"
#include "mesh.h"
#include "TexturedMesh.h"
#include "ExtruderTrain.h"
namespace cura
{
/*!
* A MeshGroup is a collection with 1 or more 3D meshes.
*
* One MeshGroup is a whole which is printed at once.
* Generally there is one single MeshGroup, though when using one-at-a-time printing, multiple MeshGroups are processed consecutively.
*/
class MeshGroup : public SettingsBase, NoCopy
{
ExtruderTrain* extruders[MAX_EXTRUDERS] = {nullptr};
int extruder_count;
public:
int getExtruderCount()
{
if (extruder_count == -1)
{
extruder_count = getSettingAsCount("machine_extruder_count");
}
return extruder_count;
}
MeshGroup(SettingsBaseVirtual* settings_base)
: SettingsBase(settings_base)
, extruder_count(-1)
{}
~MeshGroup()
{
for (unsigned int extruder = 0; extruder < MAX_EXTRUDERS; extruder++)
{
if (extruders[extruder])
{
delete extruders[extruder];
}
}
for (Mesh* mesh : meshes)
{
delete mesh;
}
}
/*!
* Create a new extruder train for the @p extruder_nr, or return the one which already exists.
*/
ExtruderTrain* createExtruderTrain(unsigned int extruder_nr)
{
if (!extruders[extruder_nr])
{
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
}
return extruders[extruder_nr];
}
ExtruderTrain* getExtruderTrain(unsigned int extruder_nr)
{
assert(extruders[extruder_nr]);
return extruders[extruder_nr];
}
std::vector<Mesh*> meshes;
Point3 min() const //! minimal corner of bounding box
{
if (meshes.size() < 1)
{
return Point3(0, 0, 0);
}
Point3 ret = meshes[0]->min();
for(unsigned int i=1; i<meshes.size(); i++)
{
Point3 v = meshes[i]->min();
ret.x = std::min(ret.x, v.x);
ret.y = std::min(ret.y, v.y);
ret.z = std::min(ret.z, v.z);
}
return ret;
}
Point3 max() const //! maximal corner of bounding box
{
if (meshes.size() < 1)
{
return Point3(0, 0, 0);
}
Point3 ret = meshes[0]->max();
for(unsigned int i=1; i<meshes.size(); i++)
{
Point3 v = meshes[i]->max();
ret.x = std::max(ret.x, v.x);
ret.y = std::max(ret.y, v.y);
ret.z = std::max(ret.z, v.z);
}
return ret;
}
void clear()
{
for(Mesh* m : meshes)
{
m->clear();
}
}
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);
}
}
};
/*!
* Load a Mesh from file and store it in the \p meshgroup.
*
* \param meshgroup The meshgroup where to store the mesh
* \param filename The filename of the mesh file
* \param transformation The transformation applied to all vertices
* \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);
}//namespace cura
#endif//MESH_GROUP_H
-178
Ver Arquivo
@@ -1,178 +0,0 @@
#ifndef PREHEAT_H
#define PREHEAT_H
#include <cassert>
#include <algorithm> // max
#include "utils/logoutput.h"
#include "MeshGroup.h"
#include "FlowTempGraph.h"
namespace cura
{
/*!
* Class for computing heatup and cooldown times used for computing the time the printer needs to heat up to a printing temperature.
*/
class Preheat
{
/*!
* The nozzle and material temperature settings for an extruder train.
*/
class Config
{
public:
double time_to_heatup_1_degree; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature)
double time_to_cooldown_1_degree; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature)
double heatup_cooldown_time_mod_while_printing; //!< The time to be added to Preheat::time_to_heatup_1_degree and subtracted from Preheat::time_to_cooldown_1_degree to get the timings while printing
double standby_temp; //!< The temperature at which the nozzle rests when it is not printing.
double material_print_temperature; //!< default print temp (backward compatilibily)
bool flow_dependent_temperature; //!< Whether to make the temperature dependent on flow
FlowTempGraph flow_temp_graph; //!< The graph linking flows to corresponding temperatures
};
std::vector<Config> config_per_extruder;//!< the nozzle and material temperature settings for each extruder train.
public:
/*!
* Get the standby temperature of an extruder train
* \param extruder the extruder train for which to get the standby tmep
* \return the standby temp
*/
double getStandbyTemp(int extruder)
{
return config_per_extruder[extruder].standby_temp;
}
/*!
* Set the nozzle and material temperature settings for each extruder train.
*/
void setConfig(MeshGroup& settings)
{
for (int extruder_nr = 0; extruder_nr < settings.getExtruderCount(); extruder_nr++)
{
assert(settings.getExtruderTrain(extruder_nr) != nullptr);
ExtruderTrain& extruder_train = *settings.getExtruderTrain(extruder_nr);
config_per_extruder.emplace_back();
Config& config = config_per_extruder.back();
config.time_to_cooldown_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed"); // 0.5
config.time_to_heatup_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed"); // 0.5
config.heatup_cooldown_time_mod_while_printing = 1.0 / extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed"); // 0.1
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature"); // 150
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature"); // 220
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
}
}
bool usesFlowDependentTemp(int extruder_nr)
{
return config_per_extruder[extruder_nr].flow_dependent_temperature;
}
private:
/*!
* Calculate time to heat up from standby temperature to a given temperature.
* Assumes @p temp is higher than the standby temperature.
*
* \param extruder The extruder for which to get the time
* \param temp The temperature to be reached
*/
double timeToHeatFromStandbyToPrintTemp(unsigned int extruder, double temp)
{
return (temp - config_per_extruder[extruder].standby_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
}
public:
/*!
* Get the optimal temperature corresponding to a given average flow.
* \param extruder The extruder train
* \param flow The flow for which to get the optimal temperature
* \return The corresponding optimal temperature
*/
double getTemp(unsigned int extruder, double flow)
{
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
}
/*!
* Decide when to start warming up again after starting to cool down towards the standby temperature.
* Two cases are considered:
* the case where the standby temperature is reached \__/ .
* and the case where it isn't \/ .
*
* IT is assumed that the printer is not printing during this cool down and warm up time.
*
* Assumes from_temp is approximately the same as @p temp
*
* \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
*/
double timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(double time_window, unsigned int extruder, double temp)
{
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)
{
return time_to_heat_from_standby_to_print_temp;
}
else
{
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);
}
}
/*!
* Calculate time needed to warm up the nozzle from a given temp to a given temp.
* If the printer is printing in the mean time the warming up will take longer.
*
*
* \param from_temp The temperature at which the nozzle was before
* \param extruder The extruder used
* \param temp The temperature to which to heat
* \param printing Whether the printer is printing in the time to heat up the nozzle
* \return The time needed to reach the desired temperature (@p temp)
*/
double timeBeforeEndToInsertPreheatCommand_warmUp(double from_temp, unsigned int extruder, double temp, bool printing)
{
if (temp > from_temp)
{
if (printing)
{
return (temp - from_temp) * (config_per_extruder[extruder].time_to_heatup_1_degree + config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
}
else
{
return (temp - from_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
}
}
else
{
if (printing)
{
return (from_temp - temp) * config_per_extruder[extruder].time_to_cooldown_1_degree;
}
else
{
return (from_temp - temp) * std::max(0.0, config_per_extruder[extruder].time_to_cooldown_1_degree - config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
}
}
}
};
} // namespace cura
#endif // PREHEAT_H
-307
Ver Arquivo
@@ -1,307 +0,0 @@
#include "PrimeTower.h"
#include "ExtruderTrain.h"
#include "sliceDataStorage.h"
#include "gcodeExport.h"
#include "gcodePlanner.h"
#include "infill.h"
#include "PrintFeature.h"
namespace cura
{
PrimeTower::PrimeTower()
{
}
void PrimeTower::initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>& retraction_config_per_extruder)
{
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
for (int extr = 0; extr < extruder_count; extr++)
{
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->getSettingInMicrons("prime_tower_line_width"), train->getSettingInPercentage("prime_tower_flow"));
}
}
void PrimeTower::setConfigs(MeshGroup* meshgroup, int layer_thickness)
{
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
for (int extr = 0; extr < extruder_count; extr++)
{
GCodePathConfig& conf = config_per_extruder[extr];
conf.setLayerHeight(layer_thickness);
}
}
void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
{ // compute storage.max_object_height_second_to_last_extruder, which is used to determine the highest point in the prime tower
extruder_count = storage.getSettingAsCount("machine_extruder_count");
int max_object_height_per_extruder[extruder_count];
std::fill_n(max_object_height_per_extruder, extruder_count, -1); // unitialize all as -1
{ // compute max_object_height_per_extruder
for (SliceMeshStorage& mesh : storage.meshes)
{
unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
max_object_height_per_extruder[extr_nr] =
std::max( max_object_height_per_extruder[extr_nr]
, mesh.layer_nr_max_filled_layer );
}
int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
max_object_height_per_extruder[support_infill_extruder_nr] =
std::max( max_object_height_per_extruder[support_infill_extruder_nr]
, storage.support.layer_nr_max_filled_layer );
int support_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] =
std::max( max_object_height_per_extruder[support_roof_extruder_nr]
, storage.support.layer_nr_max_filled_layer );
}
{ // // compute max_object_height_second_to_last_extruder
int extruder_max_object_height = 0;
for (int extruder_nr = 1; extruder_nr < extruder_count; extruder_nr++)
{
if (max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_max_object_height])
{
extruder_max_object_height = extruder_nr;
}
}
int extruder_second_max_object_height = -1;
for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
{
if (extruder_nr == extruder_max_object_height) { continue; }
if (extruder_second_max_object_height == -1 || max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_second_max_object_height])
{
extruder_second_max_object_height = extruder_nr;
}
}
if (extruder_second_max_object_height < 0)
{
storage.max_object_height_second_to_last_extruder = -1;
}
else
{
storage.max_object_height_second_to_last_extruder = max_object_height_per_extruder[extruder_second_max_object_height];
}
}
}
void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
{
PolygonRef p = storage.primeTower.ground_poly.newPoly();
int tower_size = storage.getSettingInMicrons("prime_tower_size");
int tower_distance = 0;
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
p.add(Point(x + tower_distance, y + tower_distance));
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
storage.wipePoint = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
}
void PrimeTower::generatePaths(SliceDataStorage& storage, unsigned int total_layers)
{
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0)
{
generatePaths3(storage);
}
}
void PrimeTower::generatePaths_OLD(SliceDataStorage& storage, unsigned int total_layers)
{
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0)
{
PolygonRef p = storage.primeTower.ground_poly.newPoly();
int tower_size = storage.getSettingInMicrons("prime_tower_size");
int tower_distance = 0;
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
p.add(Point(x + tower_distance, y + tower_distance));
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
storage.wipePoint = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
}
}
void PrimeTower::generatePaths2(SliceDataStorage& storage) // half baked attempt at spiral shaped prime tower pattern
{
// extruder_count = storage.getSettingAsCount("machine_extruder_count");
//
// int64_t line_dists[extruder_count + 1]; // distance between the lines of different extruders, and half the line dist for beginning and ending
// int64_t total_width = 0;
// {
// int64_t last_line_width = 0;
// for (int extr = 0; extr < extruder_count; extr++)
// {
// int64_t line_width = config_per_extruder[extr].getLineWidth();
// line_dists[extr] = (line_width + last_line_width) / 2;
// total_width += line_width;
// last_line_width = line_width;
// }
// line_dists[extruder_count] = last_line_width / 2;
// }
//
}
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?
generateGroundpoly(storage);
for (int extruder = 0; extruder < extruder_count; extruder++)
{
int line_width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("prime_tower_line_width");
patterns_per_extruder.emplace_back(n_patterns);
std::vector<Polygons>& patterns = patterns_per_extruder.back();
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
{
Polygons result_polygons; // should remain empty, since we generate lines pattern!
Polygons* in_between = nullptr;
bool avoidOverlappingPerimeters = false;
int outline_offset = -line_width/2;
int line_distance = line_width;
double fill_angle = 45 + pattern_idx * 90;
Polygons& result_lines = patterns[pattern_idx];
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, avoidOverlappingPerimeters, line_width, line_distance, infill_overlap, fill_angle);
infill_comp.generate(result_polygons, result_lines, in_between);
}
}
}
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) )
{
return;
}
bool prime_tower_added = false;
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount() && !prime_tower_added; extruder++)
{
prime_tower_added = last_prime_tower_poly_printed[extruder] == int(layer_nr);
}
if (prime_tower_added)
{ // don't print the prime tower if it has been printed already
return;
}
if (prev_extruder == gcodeLayer.getExtruder())
{
wipe = false;
}
addToGcode3(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
}
void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
{
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
{
return;
}
int new_extruder = gcodeLayer.getExtruder();
Polygons& pattern = patterns_per_extruder[new_extruder][layer_nr % 2];
GCodePathConfig& config = config_per_extruder[new_extruder];
int start_idx = 0; // TODO: figure out which idx is closest to the far right corner
gcodeLayer.addPolygon(ground_poly.back(), start_idx, &config);
gcodeLayer.addLinesByOptimizer(pattern, &config, SpaceFillType::Lines);
last_prime_tower_poly_printed[new_extruder] = layer_nr;
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Support, layer_nr, pattern, config.getLineWidth());
}
if (wipe)
{ //Make sure we wipe the old extruder on the prime tower.
gcodeLayer.addTravel(storage.wipePoint - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
}
}
void PrimeTower::addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
{
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
{
return;
}
int new_extruder = gcodeLayer.getExtruder();
int64_t offset = -config_per_extruder[new_extruder].getLineWidth();
if (layer_nr > 0)
offset *= 2;
//If we changed extruder, print the wipe/prime tower for this nozzle;
std::vector<Polygons> insets;
{ // generate polygons
if ((layer_nr % 2) == 1)
insets.push_back(storage.primeTower.ground_poly.offset(offset / 2));
else
insets.push_back(storage.primeTower.ground_poly);
while(true)
{
Polygons new_inset = insets[insets.size() - 1].offset(offset);
if (new_inset.size() < 1)
break;
insets.push_back(new_inset);
}
}
for(unsigned int n=0; n<insets.size(); n++)
{
GCodePathConfig& config = config_per_extruder[new_extruder];
gcodeLayer.addPolygonsByOptimizer(insets[(prime_tower_dir_outward)? insets.size() - 1 - n : n], &config);
}
last_prime_tower_poly_printed[new_extruder] = layer_nr;
if (wipe)
{ //Make sure we wipe the old extruder on the prime tower.
gcodeLayer.addTravel(storage.wipePoint - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
}
};
}//namespace cura
-68
Ver Arquivo
@@ -1,68 +0,0 @@
#ifndef PRIME_TOWER_H
#define PRIME_TOWER_H
#include "gcodeExport.h" // GCodePathConfig
#include "MeshGroup.h"
#include "utils/polygon.h" // Polygons
namespace cura
{
class SliceDataStorage;
class GCodePlanner;
class GCodeExport;
typedef std::vector<IntPoint> PolyLine;
class PrimeTower
{
private:
int extruder_count;
std::vector<GCodePathConfig> config_per_extruder;
class WallInfill
{
};
public:
void initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>& retraction_config_per_extruder);
void setConfigs(MeshGroup* configs, int layer_thickness);
Polygons ground_poly;
std::vector<PolyLine> extruder_paths;
void generateGroundpoly(SliceDataStorage& storage);
std::vector<std::vector<Polygons>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
void generatePaths3(SliceDataStorage& storage);
void generatePaths2(SliceDataStorage& storage);
/*!
* Generate the area where the prime tower should be.
*
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
* \param total_layers The total number of layers
*/
void generatePaths(SliceDataStorage& storage, unsigned int total_layers);
void generatePaths_OLD(SliceDataStorage& storage, unsigned int total_layers);
void computePrimeTowerMax(SliceDataStorage& storage);
PrimeTower();
void addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
void addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
void addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
};
}//namespace cura
#endif // PRIME_TOWER_H
-26
Ver Arquivo
@@ -1,26 +0,0 @@
#ifndef PRINT_FEATURE
#define PRINT_FEATURE
namespace cura
{
enum class PrintFeatureType
{
NoneType, // unused, but libArcus depends on it
OuterWall,
InnerWall,
Skin,
Support,
Skirt,
Infill,
SupportInfill,
MoveCombing,
MoveRetraction
};
} // namespace cura
#endif // PRINT_FEATURE
-101
Ver Arquivo
@@ -1,101 +0,0 @@
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
#include "Progress.h"
#include "commandSocket.h"
#include "utils/gettime.h"
namespace cura {
double Progress::times [] =
{
0.0,
5.269,
1.533,
22.953,
51.009,
48.858,
154.62,
0.1
};
std::string Progress::names [] =
{
"start",
"slice",
"layerparts",
"inset",
"support",
"skin",
"export",
"process"
};
double Progress::accumulated_times [N_PROGRESS_STAGES] = {-1};
double Progress::total_timing = -1;
/*
const Progress::Stage Progress::stages[] =
{
Progress::Stage::START,
Progress::Stage::SLICING,
Progress::Stage::PARTS,
Progress::Stage::INSET,
Progress::Stage::SUPPORT,
Progress::Stage::SKIN,
Progress::Stage::EXPORT,
Progress::Stage::FINISH
};
*/
float Progress::calcOverallProgress(Stage stage, float stage_progress)
{
return ( accumulated_times[(int)stage] + stage_progress * times[(int)stage] ) / total_timing;
}
void Progress::init()
{
double accumulated_time = 0;
for (int stage = 0; stage < N_PROGRESS_STAGES; stage++)
{
accumulated_times[(int)stage] = accumulated_time;
accumulated_time += times[(int)stage];
}
total_timing = accumulated_time;
}
void Progress::messageProgress(Progress::Stage stage, int progress_in_stage, int progress_in_stage_max)
{
float percentage = calcOverallProgress(stage, float(progress_in_stage) / float(progress_in_stage_max));
if (CommandSocket::getInstance())
{
CommandSocket::getInstance()->sendProgress(percentage);
}
logProgress(names[(int)stage].c_str(), progress_in_stage, progress_in_stage_max, percentage);
}
void Progress::messageProgressStage(Progress::Stage stage, TimeKeeper* time_keeper)
{
if (CommandSocket::getInstance())
{
CommandSocket::getInstance()->sendProgressStage(stage);
}
if (time_keeper)
{
if ((int)stage > 0)
{
log("Progress: %s accomplished in %5.3fs\n", names[(int)stage-1].c_str(), time_keeper->restart());
}
else
{
time_keeper->restart();
}
if ((int)stage < (int)Stage::FINISH)
log("Starting %s...\n", names[(int)stage].c_str());
}
}
}// namespace cura
-73
Ver Arquivo
@@ -1,73 +0,0 @@
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef PROGRESS_H
#define PROGRESS_H
#include <string>
#include "utils/logoutput.h"
#include "utils/gettime.h"
namespace cura {
class CommandSocket;
#define N_PROGRESS_STAGES 8
/*!
* Class for handling the progress bar and the progress logging.
*
* The progress bar is based on a single slicing of a rather large model which needs some complex support;
* the relative timing of each stage is currently based on that of the slicing of dragon_65_tilted_large.stl
*/
class Progress
{
public:
/*!
* The stage in the whole slicing process
*/
enum class Stage : unsigned int
{
START = 0,
SLICING = 1,
PARTS = 2,
INSET = 3,
SUPPORT = 4,
SKIN = 5,
EXPORT = 6,
FINISH = 7
};
private:
static double times [N_PROGRESS_STAGES]; //!< Time estimates per stage
static std::string names[N_PROGRESS_STAGES]; //!< name of each stage
static double accumulated_times [N_PROGRESS_STAGES]; //!< Time past before each stage
static double total_timing; //!< An estimate of the total time
/*!
* Give an estimate between 0 and 1 of how far the process is.
*
* \param stage The current stage of processing
* \param stage_process How far we currently are in the \p stage
* \return An estimate of the overall progress.
*/
static float calcOverallProgress(Stage stage, float stage_progress);
public:
static void init(); //!< Initialize some values needed in a fast computation of the progress
/*!
* Message progress over the CommandSocket and to the terminal (if the command line arg '-p' is provided).
*
* \param stage The current stage of processing
* \param progress_in_stage Any number giving the progress within the stage
* \param progress_in_stage_max The maximal value of \p progress_in_stage
*/
static void messageProgress(Stage stage, int progress_in_stage, int progress_in_stage_max);
/*!
* Message the progress stage over the command socket.
*
* \param stage The current stage
* \param timeKeeper The stapwatch keeping track of the timings for each stage (optional)
*/
static void messageProgressStage(Stage stage, TimeKeeper* timeKeeper);
};
} // name space cura
#endif//PROGRESS_H
-25
Ver Arquivo
@@ -1,25 +0,0 @@
#ifndef SPACE_FILL_TYPE
#define SPACE_FILL_TYPE
namespace cura
{
/*!
* Enum class enumerating the strategies with which an area can be occupied with filament
*
* The walls/perimeters are Polygons
* ZigZag infill is PolyLines, and so is following mesh surface mode for non-polygon surfaces
* Grid, Triangles and lines infill is Lines
*/
enum class SpaceFillType
{
None,
Polygons,
PolyLines,
Lines
};
} // namespace cura
#endif // SPACE_FILL_TYPE
-130
Ver Arquivo
@@ -1,130 +0,0 @@
#include "TextureProcessor.h"
#include <algorithm> // swap
#include "slicer/SlicerSegment.h"
namespace cura
{
#define POINT_DIST 400
#define AMPLITUDE 500
#define EXTRA_OFFSET 500
/*
void TextureProcessor::process(std::vector< Slicer* >& slicer_list)
{
for (Slicer* slicer : slicer_list)
{
for (SlicerLayer& layer : slicer->layers)
{
process(slicer->mesh, layer);
}
}
}
*/
void TextureProcessor::processBumpMap(Mesh* mesh, SlicerLayer& layer)
{
process(mesh, layer, false);
}
void TextureProcessor::processDualColorTexture(Mesh* mesh, SlicerLayer& layer)
{
process(mesh, layer, true);
}
void TextureProcessor::process(Mesh* mesh, SlicerLayer& layer, bool dual_color_texture)
{
bool flipped = false;
if (dual_color_texture)
{
flipped = layer.layer_nr % 2 == 0;
}
Polygons results;
for (PolygonRef poly : layer.polygonList)
{
// generate points in between p0 and p1
PolygonRef result = results.newPoly();
int64_t dist_left_over = (POINT_DIST / 2); // the distance to be traversed on the line before making the first new point
Point* p0 = &poly.back();
bool even = false;
for (Point& p1 : poly)
{
SlicerSegment segment(*p0, p1);
auto it = layer.segment_to_material_segment.find(segment);
if (it != layer.segment_to_material_segment.end())
{
MatSegment& mat = it->second;
MatCoord mat_start = mat.start;
MatCoord mat_end = mat.end;
if (it->first.start != *p0)
{
std::swap(mat_start, mat_end);
}
Point p0p1 = p1 - *p0;
Point perp_to_p0p1 = turn90CCW(p0p1);
int64_t p0p1_size = vSize(p0p1);
int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
// TODO: move start point (which was already moved last iteration
for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += POINT_DIST)
{
Point pa = *p0 + normal(p0p1, p0pa_dist);
if (even ^ flipped)
{
MatCoord mat_coord_now = mat_start;
mat_coord_now.coords = mat_start.coords + (mat_end.coords - mat_start.coords) * p0pa_dist / p0p1_size;
float val = mesh->getColor(mat_coord_now); // between 0 and 1
if (flipped)
{
val = 1.0f - val;
}
assert(val > -0.001 && val < 1.001);
int r = val * (AMPLITUDE * 2) - AMPLITUDE + EXTRA_OFFSET;
Point displacement = normal(perp_to_p0p1, r);
pa -= displacement;
}
result.add(pa);
dist_last_point = p0pa_dist;
even = !even;
}
// TODO: move end point as well
float val = mesh->getColor(mat_end);
int r = val * (AMPLITUDE * 2) - AMPLITUDE + EXTRA_OFFSET;
Point displacement = normal(perp_to_p0p1, r);
if (dual_color_texture)
{
result.emplace_back(p1);
}
else
{
result.emplace_back(p1 - displacement);
}
dist_left_over = p0p1_size - dist_last_point;
}
else
{
result.emplace_back(p1);
}
p0 = &p1;
}
while (result.size() < 3 )
{
unsigned int point_idx = poly.size() - 2;
result.add(poly[point_idx]);
if (point_idx == 0) { break; }
point_idx--;
}
if (result.size() < 3)
{
result.clear();
for (Point& p : poly)
result.add(p);
}
}
layer.polygonList = results;
}
}//namespace cura
-32
Ver Arquivo
@@ -1,32 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#ifndef TEXTURE_PROCESSOR_H
#define TEXTURE_PROCESSOR_H
#include <vector>
#include "slicer/Slicer.h"
#include "mesh.h"
namespace cura
{
class TextureProcessor
{
public:
/*!
* Apply offsets in the xy plane corresponding to pixel intensities
*/
static void processBumpMap(Mesh* mesh, SlicerLayer& layer);
/*!
* Apply a zigzag pattern with offsets corresponding to pixel intensities
*/
static void processDualColorTexture(Mesh* mesh, SlicerLayer& layer);
protected:
static void process(Mesh* mesh, SlicerLayer& layer, bool dual_color_texture);
};
} // namespace cura
#endif // TEXTURE_PROCESSOR_H
-137
Ver Arquivo
@@ -1,137 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#include "TexturedMesh.h"
#include <cassert>
#include "utils/logoutput.h"
namespace cura
{
TexturedMesh::TexturedMesh(SettingsBaseVirtual* sb)
: Mesh(sb)
, current_mat(-1) // not set yet
{
}
void TexturedMesh::addTextureCoord(float x, float y)
{
texture_coords.emplace_back(x, y);
}
void TexturedMesh::addFace(int vi0, int vi1, int vi2, int ti0, int ti1, int ti2)
{
if (vi0 < -1)
{
vi0 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
}
if (vi1 < -1)
{
vi1 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
}
if (vi2 < -1)
{
vi2 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
}
if (ti0 < -1)
{
ti0 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
}
if (ti1 < -1)
{
ti1 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
}
if (ti2 < -1)
{
ti2 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
}
bool made_new_face = Mesh::addFace(vi0, vi1, vi2);
if (made_new_face)
{
face_texture_indices.emplace_back(ti0, ti1, ti2, current_mat);
assert(Mesh::faces.size() == face_texture_indices.size());
}
}
bool TexturedMesh::setMaterial(std::string name)
{
current_mat = material_base.getMatId(name);
return current_mat >= 0;
}
Material* TexturedMesh::addMaterial(std::__cxx11::string name)
{
return material_base.add(name);
}
bool TexturedMesh::getFaceEdgeMatCoord(unsigned int face_idx, int64_t z, unsigned int p0_idx, unsigned int p1_idx, MatCoord& result)
{
if (face_idx >= face_texture_indices.size() || face_idx >= faces.size())
{
return false;
}
FaceTextureCoordIndices texture_idxs = face_texture_indices[face_idx];
if (texture_idxs.index[0] < 0 || texture_idxs.index[1] < 0 || texture_idxs.index[2] < 0 || texture_idxs.mat_id < 0)
{
return false;
}
MeshFace& face = faces[face_idx];
Point3 p0(vertices[face.vertex_index[p0_idx]].p);
Point3 p1(vertices[face.vertex_index[p1_idx]].p);
float dzp0 = z - p0.z;
float dp0p1 = p1.z - p0.z;
if (dzp0 * dp0p1 < 0)
{ // z doesn't lie between p0 and p1
return false;
}
if (dzp0 == 0)
{ // edge is not cut by horizontal plane!
return false;
}
float ratio = INT2MM(dzp0) / INT2MM(dp0p1);
FPoint t0 = texture_coords[texture_idxs.index[p0_idx]];
FPoint t1 = texture_coords[texture_idxs.index[p1_idx]];
result.mat_id = texture_idxs.mat_id;
result.coords.x = t0.x + (t1.x - t0.x) * ratio;
result.coords.y = t0.y + (t1.y - t0.y) * ratio;
if (result.coords.x > 1.001 || result.coords.x < -0.001 || result.coords.y > 1.001 || result.coords.y < -0.001)
{
logError("WARNING: wapping material to outside image!");
}
return true;
}
bool TexturedMesh::registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result)
{
if (!getFaceEdgeMatCoord(face_idx, z, idx_shared, idx_first, result.start))
{
return false;
}
if (!getFaceEdgeMatCoord(face_idx, z, idx_shared, idx_second, result.end))
{
return false;
}
return true;
}
float TexturedMesh::getColor(MatCoord bitmap_coord)
{
Material* mat = material_base.getMat(bitmap_coord.mat_id);
if (mat)
{
return mat->getColor(bitmap_coord.coords.x, bitmap_coord.coords.y);
}
else
{
return 0.0f;
}
}
} // namespace cura
-78
Ver Arquivo
@@ -1,78 +0,0 @@
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
#ifndef TEXTURED_MESH_H
#define TEXTURED_MESH_H
#include <vector>
#include <string>
#include "MaterialBase.h"
#include "mesh.h"
#include "utils/intpoint.h"
#include "MatSegment.h"
namespace cura
{
/*!
* A mesh with bitmap textures to it.
*
* material coordinates are defined separately, and can be reused for different bitmap textures
*/
class TexturedMesh : public Mesh
{
public:
TexturedMesh(SettingsBaseVirtual* sb);
/*!
*
*/
struct FaceTextureCoordIndices
{
int index[3]; //!< indices into texture_coords or -1 if no texture data available
int mat_id; //!< Material id
FaceTextureCoordIndices(int i1, int i2, int i3, int mat_id)
: mat_id(mat_id)
{
index[0] = i1;
index[1] = i2;
index[2] = i3;
}
};
void addTextureCoord(float x, float y);
void addFace(int vi0, int vi1, int vi2, int ti0, int ti1, int ti2);
using Mesh::addFace; // otherwise above addFace would shadow the parent addFace
bool setMaterial(std::string name); //!< set the material to be used in the comming data to be loaded
Material* addMaterial(std::string name);
virtual bool registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result);
protected:
std::vector<FPoint> texture_coords; //!< all texture coordinates by all faces
std::vector<FaceTextureCoordIndices> face_texture_indices; //!< for each face the corresponding texture coordinates in TexturedMesh::texture_coords
// TODO clean up above lists when super class clear() is called
// TODO when to clean up below material base?
MaterialBase material_base;
/*!
* Get the material coordinate corresponding to the point on a plane cutting a given edge of the face.
* \param face_idx The face for which to get the material coord
* \param z The z of the horizontal plane cutting the face
* \param p0_idx The index into the first vert of the edge
* \param p1_idx The index into the second vert of the edge
* \param result The resulting material Coordinates
* \return Whether a Material coordinate is defined at the given location
*/
bool getFaceEdgeMatCoord(unsigned int face_idx, int64_t z, unsigned int p0_idx, unsigned int p1_idx, MatCoord& result);
virtual float getColor(MatCoord bitmap_coord);
private:
int current_mat; //!< material currently used in loading the face material info
};
} // namespace cura
#endif // TEXTURED_MESH_H
+32 -50
Ver Arquivo
@@ -4,18 +4,14 @@
#include <fstream> // debug IO
#include <unistd.h>
#include "Progress.h"
#include "weaveDataStorage.h"
#include "PrintFeature.h"
namespace cura
{
void Weaver::weave(MeshGroup* meshgroup)
{
wireFrame.meshgroup = meshgroup;
int maxz = meshgroup->max().z;
void Weaver::weave(PrintObject* object, CommandSocket* commandSocket)
{
int maxz = object->max().z;
int layer_count = (maxz - initial_layer_thickness) / connectionHeight + 1;
@@ -23,12 +19,13 @@ void Weaver::weave(MeshGroup* meshgroup)
std::vector<cura::Slicer*> slicerList;
for (Mesh* mesh : meshgroup->meshes)
for(Mesh& mesh : object->meshes)
{
cura::Slicer* slicer = new cura::Slicer(mesh, initial_layer_thickness, connectionHeight, layer_count, mesh->getSettingBoolean("meshfix_keep_open_polygons"), mesh->getSettingBoolean("meshfix_extensive_stitching"));
cura::Slicer* slicer = new cura::Slicer(&mesh, initial_layer_thickness, connectionHeight, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
slicerList.push_back(slicer);
}
int starting_layer_idx;
{ // find first non-empty layer
for (starting_layer_idx = 0; starting_layer_idx < layer_count; starting_layer_idx++)
@@ -53,28 +50,20 @@ void Weaver::weave(MeshGroup* meshgroup)
for (cura::Slicer* slicer : slicerList)
wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygonList);
if (CommandSocket::isInstantiated())
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, 0, wireFrame.bottom_outline, 1);
if (commandSocket)
commandSocket->sendPolygons(Inset0Type, 0, wireFrame.bottom_outline, 1);
if (slicerList.empty()) //Wait, there is nothing to slice.
{
wireFrame.z_bottom = 0;
}
else
{
wireFrame.z_bottom = slicerList[0]->layers[starting_layer_idx].z;
}
wireFrame.z_bottom = slicerList[0]->layers[starting_layer_idx].z;
Point starting_point_in_layer;
if (wireFrame.bottom_outline.size() > 0)
starting_point_in_layer = (wireFrame.bottom_outline.max() + wireFrame.bottom_outline.min()) / 2;
else
starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2;
starting_point_in_layer = (Point(0,0) + object->max() + object->min()) / 2;
Progress::messageProgressStage(Progress::Stage::INSET, nullptr);
for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++)
{
Progress::messageProgress(Progress::Stage::INSET, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
logProgress("inset", layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
Polygons parts1;
for (cura::Slicer* slicer : slicerList)
@@ -85,10 +74,9 @@ 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 (commandSocket)
commandSocket->sendPolygons(Inset0Type, 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 +97,9 @@ void Weaver::weave(MeshGroup* meshgroup)
{
Polygons* lower_top_parts = &wireFrame.bottom_outline;
Progress::messageProgressStage(Progress::Stage::SKIN, nullptr);
for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++)
{
Progress::messageProgress(Progress::Stage::SKIN, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
logProgress("skin", layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
WeaveLayer& layer = wireFrame.layers[layer_idx];
@@ -144,21 +131,16 @@ void Weaver::weave(MeshGroup* meshgroup)
{ // roofs:
if (!wireFrame.layers.empty()) //If there are no layers, create no roof.
{
WeaveLayer& top_layer = wireFrame.layers.back();
Polygons to_be_supported; // empty for the top layer
fillRoofs(top_layer.supported, to_be_supported, -1, top_layer.z1, top_layer.roofs);
}
WeaveLayer& top_layer = wireFrame.layers.back();
Polygons to_be_supported; // empty for the top layer
fillRoofs(top_layer.supported, to_be_supported, -1, top_layer.z1, top_layer.roofs);
}
{ // bottom:
if (!wireFrame.layers.empty()) //If there are no layers, create no bottom.
{
Polygons to_be_supported; // is empty for the bottom layer, cause the order of insets doesn't really matter (in a sense everything is to be supported)
fillRoofs(wireFrame.bottom_outline, to_be_supported, -1, wireFrame.layers.front().z0, wireFrame.bottom_infill);
}
Polygons to_be_supported; // is empty for the bottom layer, cause the order of insets doesn't really matter (in a sense everything is to be supported)
fillRoofs(wireFrame.bottom_outline, to_be_supported, -1, wireFrame.layers.front().z0, wireFrame.bottom_infill);
}
}
@@ -169,7 +151,7 @@ void Weaver::createHorizontalFill(Polygons& lower_top_parts, WeaveLayer& layer,
{
int64_t bridgable_dist = connectionHeight;
// Polygons& polys_below = lower_top_parts;
Polygons& polys_below = lower_top_parts;
Polygons& polys_here = layer.supported;
Polygons& polys_above = layer_above;
@@ -209,8 +191,8 @@ void Weaver::fillRoofs(Polygons& supporting, Polygons& to_be_supported, int dire
Polygons roof_outlines;
Polygons roof_holes;
{ // split roofs into outlines and holes
std::vector<PolygonsPart> roof_parts = roofs.splitIntoParts();
for (PolygonsPart& roof_part : roof_parts)
std::vector<Polygons> roof_parts = roofs.splitIntoParts();
for (Polygons& roof_part : roof_parts)
{
roof_outlines.add(roof_part[0]);
for (unsigned int hole_idx = 1; hole_idx < roof_part.size(); hole_idx++)
@@ -224,8 +206,8 @@ void Weaver::fillRoofs(Polygons& supporting, Polygons& to_be_supported, int dire
Polygons supporting_outlines;
std::vector<PolygonsPart> supporting_parts = supporting.splitIntoParts();
for (PolygonsPart& supporting_part : supporting_parts)
std::vector<Polygons> supporting_parts = supporting.splitIntoParts();
for (Polygons& supporting_part : supporting_parts)
supporting_outlines.add(supporting_part[0]); // only add outlines, not the holes
@@ -271,11 +253,11 @@ void Weaver::fillFloors(Polygons& supporting, Polygons& to_be_supported, int dir
if (floors.size() == 0) return;
std::vector<PolygonsPart> floor_parts = floors.splitIntoParts();
std::vector<Polygons> floor_parts = floors.splitIntoParts();
Polygons floor_outlines;
Polygons floor_holes;
for (PolygonsPart& floor_part : floor_parts)
for (Polygons& floor_part : floor_parts)
{
floor_outlines.add(floor_part[0]);
for (unsigned int hole_idx = 1; hole_idx < floor_part.size(); hole_idx++)
@@ -394,7 +376,7 @@ void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons&
{
const PolygonRef upperPart = parts1[prt];
ClosestPolygonPoint closestInPoly = PolygonUtils::findClosest(start_close_to, upperPart);
ClosestPolygonPoint closestInPoly = findClosest(start_close_to, upperPart);
PolygonRef part_top = result.newPoly();
@@ -405,7 +387,7 @@ void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons&
for (Point upper_point = upperPart[closestInPoly.pos]; found; upper_point = next_upper.location)
{
found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.pos, next_upper);
found = getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.pos, next_upper);
if (!found)
@@ -455,7 +437,7 @@ void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported,
for (const Point& upper_point : upperPart)
{
ClosestPolygonPoint lowerPolyPoint = PolygonUtils::findClosest(upper_point, supporting);
ClosestPolygonPoint lowerPolyPoint = findClosest(upper_point, supporting);
Point& lower = lowerPolyPoint.location;
Point3 lower3 = Point3(lower.X, lower.Y, z0);
@@ -487,5 +469,5 @@ void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported,
}//namespace cura
} // namespace cura
+8 -7
Ver Arquivo
@@ -5,10 +5,9 @@
#include "commandSocket.h"
#include "settings.h"
#include "MeshGroup.h"
#include "slicer/Slicer.h"
#include "modelFile/modelFile.h" // PrintObject
#include "slicer.h"
#include "utils/NoCopy.h"
#include "utils/polygon.h"
#include "utils/polygonUtils.h"
@@ -20,7 +19,7 @@ namespace cura
/*!
* The main weaver / WirePrint / wireframe printing class, which computes the basic paths to be followed.
*/
class Weaver : public SettingsMessenger, NoCopy
class Weaver : public SettingsBase
{
friend class Wireframe2gcode;
private:
@@ -41,7 +40,8 @@ private:
public:
Weaver(SettingsBase* settings_base) : SettingsMessenger(settings_base)
Weaver(SettingsBase* settings_base) : SettingsBase(settings_base)
{
initial_layer_thickness = getSettingInMicrons("layer_height_0");
@@ -60,9 +60,10 @@ public:
* This is the main function for Neith / Weaving / WirePrinting / Webbed printing.
* Creates a wireframe for the model consisting of horizontal 'flat' parts and connections between consecutive flat parts consisting of UP moves and diagonally DOWN moves.
*
* \param objects The objects for which to create a wireframe print
* \param object The object for which to create a wireframe print
* \param commandSocket the commandSocket
*/
void weave(MeshGroup* objects);
void weave(PrintObject* object, CommandSocket* commandSocket);
private:
+46 -114
Ver Arquivo
@@ -4,38 +4,39 @@
#include <fstream> // debug IO
#include "weaveDataStorage.h"
#include "Progress.h"
#include "pathOrderOptimizer.h" // for skirt
namespace cura
{
void Wireframe2gcode::writeGCode()
void Wireframe2gcode::writeGCode(CommandSocket* commandSocket, int& maxObjectHeight)
{
gcode.preSetup(wireFrame.meshgroup);
if (commandSocket)
commandSocket->beginGCode();
if (CommandSocket::getInstance())
CommandSocket::getInstance()->beginGCode();
maxObjectHeight = wireFrame.layers.back().z1;
processStartingCode();
int maxObjectHeight;
if (wireFrame.layers.empty())
{
maxObjectHeight = 0;
}
else
{
maxObjectHeight = wireFrame.layers.back().z1;
{ // starting Gcode
if (hasSetting("material_bed_temperature") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), true);
if (hasSetting("material_print_temperature") && getSettingInDegreeCelsius("material_print_temperature") > 0)
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"));
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
if (gcode.getFlavor() == GCODE_FLAVOR_BFB)
{
gcode.writeComment("enable auto-retraction");
std::ostringstream tmp;
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
gcode.writeLine(tmp.str().c_str());
}
}
processSkirt();
unsigned int total_layers = wireFrame.layers.size();
unsigned int totalLayers = wireFrame.layers.size();
gcode.writeLayerComment(0);
gcode.writeTypeComment("SKIRT");
@@ -76,16 +77,18 @@ void Wireframe2gcode::writeGCode()
gcode.writeMove(segment.to, speedBottom, extrusion_per_mm_flat);
}
);
Progress::messageProgressStage(Progress::Stage::EXPORT, nullptr);
for (unsigned int layer_nr = 0; layer_nr < wireFrame.layers.size(); layer_nr++)
{
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers); // abuse the progress system of the normal mode of CuraEngine
logProgress("export", layer_nr+1, totalLayers); // abuse the progress system of the normal mode of CuraEngine
if (commandSocket) commandSocket->sendProgress(2.0/3.0 + 1.0/3.0 * float(layer_nr) / float(totalLayers));
WeaveLayer& layer = wireFrame.layers[layer_nr];
gcode.writeLayerComment(layer_nr+1);
double fanSpeed = getSettingInPercentage("cool_fan_speed_max");
int fanSpeed = getSettingInPercentage("cool_fan_speed_max");
if (layer_nr == 0)
fanSpeed = getSettingInPercentage("cool_fan_speed_min");
gcode.writeFanCommand(fanSpeed);
@@ -165,7 +168,15 @@ void Wireframe2gcode::writeGCode()
gcode.writeFanCommand(0);
finalize();
if (commandSocket)
{
gcode.finalize(maxObjectHeight, getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
for(int e=0; e<MAX_EXTRUDERS; e++)
gcode.writeTemperatureCommand(e, 0, false);
commandSocket->sendGCodeLayer();
commandSocket->endSendSlicedObject();
}
}
@@ -233,14 +244,11 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
RetractionConfig retraction_config;
// TODO: get these from the settings!
retraction_config.distance = 500; //INT2MM(getSettingInt("retraction_amount"))
retraction_config.prime_volume = 0;//INT2MM(getSettingInt("retractionPrime
retraction_config.amount = 500; //INT2MM(getSettingInt("retractionAmount"))
retraction_config.primeAmount = 0;//INT2MM(getSettingInt("retractionPrime
retraction_config.speed = 20; // 40;
retraction_config.primeSpeed = 15; // 30;
retraction_config.zHop = 0; //getSettingInt("retraction_hop");
retraction_config.retraction_count_max = getSettingAsCount("retraction_count_max");
retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
double top_retract_pause = 2.0;
int retract_hop_dist = 1000;
@@ -398,16 +406,16 @@ void Wireframe2gcode::handle_roof_segment(WeaveRoofPart& inset, WeaveConnectionP
void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polygons& roof_outlines
void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& fill_insets, Polygons& roof_outlines
, std::function<void (Wireframe2gcode& thiss, WeaveRoofPart& inset, WeaveConnectionPart& part, unsigned int segment_idx)> connectionHandler
, std::function<void (Wireframe2gcode& thiss, WeaveConnectionSegment& p)> flatHandler)
{
// bottom:
gcode.writeTypeComment("FILL");
for (unsigned int inset_idx = 0; inset_idx < infill_insets.size(); inset_idx++)
for (unsigned int inset_idx = 0; inset_idx < fill_insets.size(); inset_idx++)
{
WeaveRoofPart& inset = infill_insets[inset_idx];
WeaveRoofPart& inset = fill_insets[inset_idx];
for (unsigned int inset_part_nr = 0; inset_part_nr < inset.connections.size(); inset_part_nr++)
@@ -476,10 +484,10 @@ void Wireframe2gcode::writeMoveWithRetract(Point to)
}
Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBase* settings_base)
: SettingsMessenger(settings_base)
: SettingsBase(settings_base)
, gcode(gcode)
, wireFrame(weaver.wireFrame)
{
wireFrame = weaver.wireFrame;
initial_layer_thickness = getSettingInMicrons("layer_height_0");
connectionHeight = getSettingInMicrons("wireframe_height");
roof_inset = getSettingInMicrons("wireframe_roof_inset");
@@ -492,8 +500,8 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
double filament_area = /* M_PI * */ (INT2MM(filament_diameter) / 2.0) * (INT2MM(filament_diameter) / 2.0);
double lineArea = /* M_PI * */ (INT2MM(extrusionWidth) / 2.0) * (INT2MM(extrusionWidth) / 2.0);
extrusion_per_mm_connection = lineArea / filament_area * flowConnection / 100.0;
extrusion_per_mm_flat = lineArea / filament_area * flowFlat / 100.0;
extrusion_per_mm_connection = lineArea / filament_area * double(flowConnection) / 100.0;
extrusion_per_mm_flat = lineArea / filament_area * double(flowFlat) / 100.0;
nozzle_outer_diameter = getSettingInMicrons("machine_nozzle_tip_outer_diameter"); // ___ ___ .
nozzle_head_distance = getSettingInMicrons("machine_nozzle_head_distance"); // | | .
@@ -534,90 +542,14 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
roof_outer_delay = getSettingInSeconds("wireframe_roof_outer_delay");
standard_retraction_config.distance = INT2MM(getSettingInMicrons("retraction_amount"));
standard_retraction_config.prime_volume = getSettingInCubicMillimeters("retraction_extra_prime_amount");
standard_retraction_config.amount = INT2MM(getSettingInMicrons("retraction_amount"));
standard_retraction_config.primeAmount = INT2MM(getSettingInMicrons("retraction_extra_prime_amount"));
standard_retraction_config.speed = getSettingInMillimetersPerSecond("retraction_retract_speed");
standard_retraction_config.primeSpeed = getSettingInMillimetersPerSecond("retraction_prime_speed");
standard_retraction_config.zHop = getSettingInMicrons("retraction_hop");
standard_retraction_config.retraction_count_max = getSettingAsCount("retraction_count_max");
standard_retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
standard_retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
}
void Wireframe2gcode::processStartingCode()
{
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
{
if (!CommandSocket::isInstantiated())
{
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
}
}
else
{
if (getSettingBoolean("material_bed_temp_prepend"))
{
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
{
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), getSettingBoolean("material_bed_temp_wait"));
}
}
if (getSettingBoolean("material_print_temp_prepend"))
{
if (getSettingInDegreeCelsius("material_print_temperature") > 0)
{
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"));
if (getSettingBoolean("machine_print_temp_wait"))
{
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), 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");
std::ostringstream tmp;
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000);
gcode.writeLine(tmp.str().c_str());
}
}
void Wireframe2gcode::processSkirt()
{
if (wireFrame.bottom_outline.size() == 0) //If we have no layers, don't create a skirt either.
{
return;
}
Polygons skirt = wireFrame.bottom_outline.offset(100000+5000).offset(-100000);
PathOrderOptimizer order(Point(INT32_MIN, INT32_MIN));
order.addPolygons(skirt);
order.optimize();
for (unsigned int poly_order_idx = 0; poly_order_idx < skirt.size(); poly_order_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);
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
Point& p = poly[(point_idx + order.polyStart[poly_idx] + 1) % poly.size()];
gcode.writeMove(p, getSettingInMillimetersPerSecond("skirt_speed"), getSettingInMillimetersPerSecond("skirt_line_width"));
}
}
}
void Wireframe2gcode::finalize()
{
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);
}
}//namespace cura
} // namespace cura
+35 -52
Ver Arquivo
@@ -4,14 +4,12 @@
#include <functional> // passing function pointer or lambda as argument to a function
#include "utils/NoCopy.h"
#include "weaveDataStorage.h"
#include "commandSocket.h"
#include "settings.h"
#include "MeshGroup.h"
#include "slicer/Slicer.h"
#include "modelFile/modelFile.h" // PrintObject
#include "slicer.h"
#include "utils/polygon.h"
#include "Weaver.h"
@@ -24,7 +22,7 @@ namespace cura
/*!
* Export class for exporting wireframe print gcode / weaver gcode / wireprint gcode.
*/
class Wireframe2gcode : public SettingsMessenger, NoCopy
class Wireframe2gcode : public SettingsBase
{
private:
static const int STRATEGY_COMPENSATE = 0;
@@ -34,35 +32,35 @@ private:
int initial_layer_thickness;
int filament_diameter;
int extrusionWidth;
double flowConnection;
double flowFlat;
double extrusion_per_mm_connection;
double extrusion_per_mm_flat;
int nozzle_outer_diameter;
int nozzle_head_distance;
double nozzle_expansion_angle;
int nozzle_clearance;
int nozzle_top_diameter;
double moveSpeed;
double speedBottom;
double speedUp;
double speedDown;
double speedFlat;
int connectionHeight;
int roof_inset;
double flat_delay;
double bottom_delay;
double top_delay;
int up_dist_half_speed;
int top_jump_dist;
int fall_down;
int drag_along;
int strategy;
double go_back_to_last_top;
int straight_first_when_going_down;
int roof_fall_down;
int roof_drag_along;
double roof_outer_delay;
int flowConnection;// = getSettingInt("wireframeFlowConnection");
int flowFlat; // = getSettingInt("wireframeFlowFlat");
double extrusion_per_mm_connection; // = lineArea / filament_area * double(flowConnection) / 100.0;
double extrusion_per_mm_flat; // = lineArea / filament_area * double(flowFlat) / 100.0;
int nozzle_outer_diameter; // = getSettingInt("machineNozzleTipOuterDiameter"); // ___ ___ .
int nozzle_head_distance; // = getSettingInt("machineNozzleHeadDistance"); // | | .
int nozzle_expansion_angle; // = getSettingInt("machineNozzleExpansionAngle"); // \_U_/ .
int nozzle_clearance; // = getSettingInt("wireframeNozzleClearance"); // at least line width
int nozzle_top_diameter; // = tan(static_cast<double>(nozzle_expansion_angle)/180.0 * M_PI) * connectionHeight + nozzle_outer_diameter + nozzle_clearance;
int moveSpeed; // = 40;
int speedBottom; // = getSettingInt("wireframePrintspeedBottom");
int speedUp; // = getSettingInt("wireframePrintspeedUp");
int speedDown; // = getSettingInt("wireframePrintspeedDown");
int speedFlat; // = getSettingInt("wireframePrintspeedFlat");
int connectionHeight; // = getSettingInt("wireframeConnectionHeight");
int roof_inset; // = getSettingInt("wireframeRoofInset");
double flat_delay; // = getSettingInt("wireframeFlatDelay")/100.0;
double bottom_delay; // = getSettingInt("wireframeBottomDelay")/100.0;
double top_delay; // = getSettingInt("wireframeTopDelay")/100.0;
int up_dist_half_speed; // = getSettingInt("wireframeUpDistHalfSpeed");
int top_jump_dist; // = getSettingInt("wireframeTopJump");
int fall_down; // = getSettingInt("wireframeFallDown");
int drag_along; // = getSettingInt("wireframeDragAlong");
int strategy; // = getSettingInt("wireframeStrategy"); // HIGHER_BEND_NO_STRAIGHTEN; // RETRACT_TO_STRAIGHTEN; // MOVE_TO_STRAIGHTEN; //
double go_back_to_last_top; // = false;
int straight_first_when_going_down; // = getSettingInt("wireframeStraightBeforeDown"); // %
int roof_fall_down; // = getSettingInt("wireframeRoofFallDown");
int roof_drag_along; // = getSettingInt("wireframeRoofDragAlong");
double roof_outer_delay; // = getSettingInt("wireframeRoofOuterDelay")/100.0;
RetractionConfig standard_retraction_config; //!< The standard retraction settings used for moves between parts etc.
@@ -71,28 +69,13 @@ public:
Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBase* settings_base);
void writeGCode();
void writeGCode(CommandSocket* commandSocket, int& maxObjectHeight);
private:
WireFrame& wireFrame;
WireFrame wireFrame;
/*!
* Startup gcode: nozzle temp up, retraction settings, bed temp
*/
void processStartingCode();
/*!
* Lay down a skirt
*/
void processSkirt();
/*!
* End gcode: nozzle temp down
*/
void finalize();
void writeFill(std::vector<WeaveRoofPart>& infill_insets, Polygons& outlines
void writeFill(std::vector<WeaveRoofPart>& fill_insets, Polygons& outlines
, std::function<void (Wireframe2gcode& thiss, WeaveRoofPart& inset, WeaveConnectionPart& part, unsigned int segment_idx)> connectionHandler
, std::function<void (Wireframe2gcode& thiss, WeaveConnectionSegment& p)> flatHandler);
-2
Ver Arquivo
@@ -1,8 +1,6 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "bridge.h"
#include "sliceDataStorage.h"
namespace cura {
int bridgeAngle(Polygons outline, SliceLayer* prevLayer)
+2 -2
Ver Arquivo
@@ -2,9 +2,9 @@
#ifndef BRIDGE_H
#define BRIDGE_H
#include "sliceDataStorage.h"
namespace cura {
class Polygons;
class SliceLayer;
int bridgeAngle(Polygons outline, SliceLayer* prevLayer);
+186 -341
Ver Arquivo
@@ -1,391 +1,236 @@
/** 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 !!
bool Comb::preTest(Point startPoint, Point endPoint)
{
return collisionTest(startPoint, endPoint);
}
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()
bool Comb::collisionTest(Point startPoint, Point endPoint)
{
Point diff = endPoint - startPoint;
transformation_matrix = PointMatrix(diff);
transformed_startPoint = transformation_matrix.apply(startPoint);
transformed_endPoint = transformation_matrix.apply(endPoint);
for(PolygonRef poly : boundary)
matrix = PointMatrix(diff);
sp = matrix.apply(startPoint);
ep = matrix.apply(endPoint);
for(unsigned int n=0; n<boundery.size(); n++)
{
Point p0 = transformation_matrix.apply(poly.back());
for(Point p1_ : poly)
if (boundery[n].size() < 1)
continue;
Point p0 = matrix.apply(boundery[n][boundery[n].size()-1]);
for(unsigned int i=0; i<boundery[n].size(); i++)
{
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))
Point p1 = matrix.apply(boundery[n][i]);
if ((p0.Y > sp.Y && p1.Y < sp.Y) || (p1.Y > sp.Y && p0.Y < sp.Y))
{
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
int64_t x = p0.X + (p1.X - p0.X) * (sp.Y - p0.Y) / (p1.Y - p0.Y);
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
if (x > sp.X && x < ep.X)
return true;
}
p0 = p1;
}
}
return false;
}
void LinePolygonsCrossings::getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored)
void Comb::calcMinMax()
{
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored) || !lineSegmentCollidesWithBoundary())
for(unsigned int n=0; n<boundery.size(); n++)
{
//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))
minX[n] = INT64_MAX;
maxX[n] = INT64_MIN;
Point p0 = matrix.apply(boundery[n][boundery[n].size()-1]);
for(unsigned int i=0; i<boundery[n].size(); i++)
{
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
Point p1 = matrix.apply(boundery[n][i]);
if ((p0.Y > sp.Y && p1.Y < sp.Y) || (p1.Y > sp.Y && p0.Y < sp.Y))
{
int64_t x = p0.X + (p1.X - p0.X) * (sp.Y - p0.Y) / (p1.Y - p0.Y);
if (x >= sp.X && x <= ep.X)
{
if (x < minX[n]) { minX[n] = x; minIdx[n] = i; }
if (x > maxX[n]) { maxX[n] = x; maxIdx[n] = i; }
}
}
p0 = p1;
}
}
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)
unsigned int Comb::getPolygonAbove(int64_t x)
{
PolyCrossings* ret = nullptr;
for(PolyCrossings& crossing : crossings)
int64_t min = POINT_MAX;
unsigned int ret = NO_INDEX;
for(unsigned int n=0; n<boundery.size(); n++)
{
if (crossing.min.x > x && (ret == nullptr || crossing.min.x < ret->min.x) )
if (minX[n] > x && minX[n] < min)
{
ret = &crossing;
min = minX[n];
ret = n;
}
}
return ret;
}
bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimized_comb_path)
Point Comb::getBounderyPointWithOffset(unsigned int polygonNr, unsigned int idx)
{
optimized_comb_path.push_back(startPoint);
for(unsigned int point_idx = 1; point_idx<comb_path.size(); point_idx++)
Point p0 = boundery[polygonNr][(idx > 0) ? (idx - 1) : (boundery[polygonNr].size() - 1)];
Point p1 = boundery[polygonNr][idx];
Point p2 = boundery[polygonNr][(idx < (boundery[polygonNr].size() - 1)) ? (idx + 1) : (0)];
Point off0 = crossZ(normal(p1 - p0, MM2INT(1.0)));
Point off1 = crossZ(normal(p2 - p1, MM2INT(1.0)));
Point n = normal(off0 + off1, MM2INT(0.2));
return p1 + n;
}
Comb::Comb(Polygons& _boundery)
: boundery(_boundery)
{
minX = new int64_t[boundery.size()];
maxX = new int64_t[boundery.size()];
minIdx = new unsigned int[boundery.size()];
maxIdx = new unsigned int[boundery.size()];
}
Comb::~Comb()
{
delete[] minX;
delete[] maxX;
delete[] minIdx;
delete[] maxIdx;
}
bool Comb::moveInside(Point* p, int distance)
{
Point ret = *p;
int64_t bestDist = MM2INT(2.0) * MM2INT(2.0);
for(unsigned int n=0; n<boundery.size(); n++)
{
if(comb_path[point_idx] == comb_path[point_idx - 1]) //Two points are the same. Skip the second.
{
if (boundery[n].size() < 1)
continue;
}
Point& current_point = optimized_comb_path.back();
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
Point p0 = boundery[n][boundery[n].size()-1];
for(unsigned int i=0; i<boundery[n].size(); i++)
{
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
Point p1 = boundery[n][i];
// 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)
//Q = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
Point pDiff = p1 - p0;
int64_t lineLength = vSize(pDiff);
int64_t distOnLine = dot(pDiff, *p - p0) / lineLength;
if (distOnLine < 10)
distOnLine = 10;
if (distOnLine > lineLength - 10)
distOnLine = lineLength - 10;
Point q = p0 + pDiff * distOnLine / lineLength;
int64_t dist = vSize2(q - *p);
if (dist < bestDist)
{
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
{
break;
}
else
{
optimized_comb_path.pop_back();
}
bestDist = dist;
ret = q + crossZ(normal(p1 - p0, distance));
}
p0 = p1;
}
}
optimized_comb_path.push_back(comb_path.back());
if (bestDist < MM2INT(2.0) * MM2INT(2.0))
{
*p = ret;
return true;
}
return false;
}
bool Comb::calc(Point startPoint, Point endPoint, std::vector<Point>& combPoints)
{
if (shorterThen(endPoint - startPoint, MM2INT(1.5)))
return true;
bool addEndpoint = false;
//Check if we are inside the comb boundaries
if (!boundery.inside(startPoint))
{
if (!moveInside(&startPoint)) //If we fail to move the point inside the comb boundary we need to retract.
return false;
combPoints.push_back(startPoint);
}
if (!boundery.inside(endPoint))
{
if (!moveInside(&endPoint)) //If we fail to move the point inside the comb boundary we need to retract.
return false;
addEndpoint = true;
}
//Check if we are crossing any bounderies, and pre-calculate some values.
if (!preTest(startPoint, endPoint))
{
//We're not crossing any boundaries. So skip the comb generation.
if (!addEndpoint && combPoints.size() == 0) //Only skip if we didn't move the start and end point.
return true;
}
//Calculate the minimum and maximum positions where we cross the comb boundary
calcMinMax();
int64_t x = sp.X;
std::vector<Point> pointList;
//Now walk trough the crossings, for every boundary we cross, find the initial cross point and the exit point. Then add all the points in between
// to the pointList and continue with the next boundary we will cross, until there are no more boundaries to cross.
// This gives a path from the start to finish curved around the holes that it encounters.
while(true)
{
unsigned int n = getPolygonAbove(x);
if (n == NO_INDEX) break;
pointList.push_back(matrix.unapply(Point(minX[n] - MM2INT(0.2), sp.Y)));
if ( (minIdx[n] - maxIdx[n] + boundery[n].size()) % boundery[n].size() > (maxIdx[n] - minIdx[n] + boundery[n].size()) % boundery[n].size())
{
for(unsigned int i=minIdx[n]; i != maxIdx[n]; i = (i < boundery[n].size() - 1) ? (i + 1) : (0))
{
pointList.push_back(getBounderyPointWithOffset(n, i));
}
}else{
if (minIdx[n] == 0)
minIdx[n] = boundery[n].size() - 1;
else
minIdx[n]--;
if (maxIdx[n] == 0)
maxIdx[n] = boundery[n].size() - 1;
else
maxIdx[n]--;
for(unsigned int i=minIdx[n]; i != maxIdx[n]; i = (i > 0) ? (i - 1) : (boundery[n].size() - 1))
{
pointList.push_back(getBounderyPointWithOffset(n, i));
}
}
pointList.push_back(matrix.unapply(Point(maxX[n] + MM2INT(0.2), sp.Y)));
x = maxX[n];
}
pointList.push_back(endPoint);
//Optimize the pointList, skip each point we could already reach by not crossing a boundary. This smooths out the path and makes it skip any unneeded corners.
Point p0 = startPoint;
for(unsigned int n=1; n<pointList.size(); n++)
{
if (collisionTest(p0, pointList[n]))
{
if (collisionTest(p0, pointList[n-1]))
return false;
p0 = pointList[n-1];
combPoints.push_back(p0);
}
}
if (addEndpoint)
combPoints.push_back(endPoint);
return true;
}
+21 -234
Ver Arquivo
@@ -4,252 +4,39 @@
#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
{
};
namespace cura {
/*!
* Class for generating a combing move action from point a to point b and avoiding collision with other parts when moving through air.
* See LinePolygonsCrossings::comb.
*
* The general implementation is by rotating everything such that the the line segment from a to b is aligned with the x-axis.
* We call the line on which a and b lie the 'scanline'.
*
* The basic path is generated by following the scanline until it hits a polygon, then follow the polygon until the last point where it hits the scanline,
* follow the scanline again, etc.
* The path is offsetted from the polygons, so that it doesn't intersect with them.
*
* Next the basic path is optimized by taking shortcuts where possible. Only shortcuts which skip a single point are considered, in order to reduce computational complexity.
*/
class LinePolygonsCrossings
class Comb
{
private:
/*!
* A Crossing holds data on a single point where a polygon crosses the scanline.
*/
struct Crossing
{
int64_t x; //!< x coordinate of crossings between the polygon and the scanline.
unsigned int point_idx; //!< The index of the first point of the line segment which crosses the scanline
/*!
* Creates a Crossing with minimal initialization
* \param x The x-coordinate in transformed space
* \param point_idx The index of the first point of the line segment which crosses the scanline
*/
Crossing(int64_t x, unsigned int point_idx)
: x(x), point_idx(point_idx)
{
}
};
/*!
* A PolyCrossings holds data on where a polygon crosses the scanline. Only the Crossing with lowest Crossing::x and highest are recorded.
*/
struct PolyCrossings
{
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.
/*!
* 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
*/
PolyCrossings(unsigned int poly_idx)
: poly_idx(poly_idx)
, min(INT64_MAX, NO_INDEX), max(INT64_MIN, NO_INDEX)
{
}
};
Polygons& boundery;
/*!
* A PolyCrossings list: for every polygon a PolyCrossings.
*/
struct PartCrossings : public std::vector<PolyCrossings>
{
//unsigned int part_idx;
};
PartCrossings crossings; //!< All crossings of polygons in the LinePolygonsCrossings::boundary with the scanline.
unsigned int min_crossing_idx; //!< The index into LinePolygonsCrossings::crossings to the crossing with the minimal PolyCrossings::min crossing of all PolyCrossings's.
unsigned int max_crossing_idx; //!< The index into LinePolygonsCrossings::crossings to the crossing with the maximal PolyCrossings::max crossing of all PolyCrossings's.
Polygons& boundary; //!< The boundary not to cross during combing.
Point startPoint; //!< The start point of the scanline.
Point endPoint; //!< The end point of the scanline.
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
Point transformed_endPoint; //!< The LinePolygonsCrossings::endPoint as transformed by Comb::transformation_matrix
int64_t* minX;
int64_t* maxX;
unsigned int* minIdx;
unsigned int* maxIdx;
/*!
* Check if we are crossing the boundaries, and pre-calculate some values.
*
* Sets Comb::transformation_matrix, Comb::transformed_startPoint and Comb::transformed_endPoint
* \return Whether the line segment from LinePolygonsCrossings::startPoint to LinePolygonsCrossings::endPoint collides with the boundary
*/
bool lineSegmentCollidesWithBoundary();
/*!
* Calculate Comb::crossings, Comb::min_crossing_idx and Comb::max_crossing_idx.
*/
void calcScanlineCrossings();
/*!
* Get the basic combing path and optimize it.
*
* \param combPath Output parameter: the points along the combing path.
*/
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.
*
* Walk trough the crossings, for every boundary we cross, find the initial cross point and the exit point. Then add all the points in between
* to the \p combPath and continue with the next boundary we will cross, until there are no more boundaries to cross.
* This gives a path from the start to finish curved around the holes that it encounters.
*
* \param combPath Output parameter: the points along the combing path.
*/
void getBasicCombingPath(CombPath& combPath);
/*!
* Get the basic combing path, following a single boundary polygon when it hits it, until it passes the scanline again.
*
* Find the initial cross point and the exit point. Then add all the points in between
* to the \p combPath and continue with the next boundary we will cross, until there are no more boundaries to cross.
* This gives a path from the start to finish curved around the polygon that it encounters.
*
* \param combPath Output parameter: where to add the points along the combing path.
*/
void getBasicCombingPath(PolyCrossings& crossings, CombPath& combPath);
/*!
* Find the first polygon cutting the scanline after \p x.
*
* Note that this function only looks at the first segment cutting the scanline (see Comb::minX)!
* It doesn't return the next polygon which crosses the scanline, but the first polygon crossing the scanline for the first time.
*
* \param x The point on the scanline from where to look.
* \return The next PolyCrossings fully beyond \p x or one with PolyCrossings::poly_idx set to NO_INDEX if there's none left.
*/
PolyCrossings* getNextPolygonAlongScanline(int64_t x);
/*!
* Optimize the \p comb_path: skip each point we could already reach by not crossing a boundary. This smooths out the path and makes it skip some unneeded corners.
*
* \param comb_path The unoptimized combing path.
* \param optimized_comb_path Output parameter: The points of optimized combing path
* \return Whether it turns out that the basic comb path already crossed a boundary
*/
bool optimizePath(CombPath& comb_path, CombPath& optimized_comb_path);
/*!
* Create a LinePolygonsCrossings with minimal initialization.
* \param boundary The boundary which not to cross during combing
* \param start the starting point
* \param end the end point
* \param dist_to_move_boundary_point_outside Distance used to move a point from a boundary so that it doesn't intersect with it anymore. (Precision issue)
*/
LinePolygonsCrossings(Polygons& boundary, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
: boundary(boundary), startPoint(start), endPoint(end), dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
{
}
public:
/*!
* The main function of this class: calculate one combing path within the boundary.
* \param boundary The polygons to follow when calculating the basic combing path
* \param startPoint From where to start the combing move.
* \param endPoint Where to end the combing move.
* \param combPath Output parameter: the combing path generated.
*/
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);
linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored);
};
};
PointMatrix matrix;
Point sp;
Point ep;
class SliceDataStorage;
bool preTest(Point startPoint, Point endPoint);
bool collisionTest(Point startPoint, Point endPoint);
/*!
* 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.
void calcMinMax();
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.
unsigned int getPolygonAbove(int64_t x);
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();
Point getBounderyPointWithOffset(unsigned int polygonNr, unsigned int idx);
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(Polygons& _boundery);
~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));
bool inside(const Point p) { return boundery.inside(p); }
bool moveInside(Point* p, int distance = 100);
bool calc(Point startPoint, Point endPoint, std::vector<Point>& combPoints);
};
}//namespace cura
+115 -318
Ver Arquivo
@@ -1,373 +1,202 @@
#include "utils/logoutput.h"
#include "commandSocket.h"
#include "FffProcessor.h"
#include "Progress.h"
#include "fffProcessor.h"
#include <thread>
#include <cinttypes>
#ifdef ARCUS
#include <Arcus/Socket.h>
#include <Arcus/SocketListener.h>
#include <Arcus/Error.h>
#endif
#include <string> // stoi
#ifdef _WIN32
#include <windows.h>
#endif
#define DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(x)
// std::cerr << x;
namespace cura {
#define BYTES_PER_FLOAT 4
#define FLOATS_PER_VECTOR 3
#define VECTORS_PER_FACE 3
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)
: processor(nullptr)
, socket(nullptr)
, object_count(0)
, current_sliced_object(nullptr)
, sliced_objects(0)
, current_layer_count(0)
, current_layer_offset(0)
, current_object_number(0)
, currentSlicedObject(nullptr)
, slicedObjects(0)
{ }
cura::proto::Layer* getLayerById(int id);
Cura::Layer* getLayerById(int id);
fffProcessor* processor;
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;
int current_object_number;
// Number of layers sent to the front end so far
// 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::shared_ptr<Cura::SlicedObjectList> slicedObjectList;
Cura::SlicedObject* currentSlicedObject;
int slicedObjects;
std::vector<int64_t> objectIds;
std::string temp_gcode_file;
std::string tempGCodeFile;
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, cura::proto::Layer*> sliced_layers;
std::shared_ptr<PrintObject> objectToSlice;
};
#endif
CommandSocket::CommandSocket()
#ifdef ARCUS
: private_data(new Private)
#endif
CommandSocket::CommandSocket(fffProcessor* processor)
: d(new Private)
{
#ifdef ARCUS
#endif
d->processor = processor;
d->processor->setCommandSocket(this);
}
CommandSocket* CommandSocket::getInstance()
{
return instance;
}
void CommandSocket::instantiate()
{
instance = new CommandSocket();
}
bool CommandSocket::isInstantiated()
{
return instance != nullptr;
}
void CommandSocket::connect(const std::string& ip, int port)
{
#ifdef ARCUS
private_data->socket = new Arcus::Socket();
private_data->socket->addListener(new Listener());
d->socket = new Arcus::Socket();
d->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
d->socket->registerMessageType(2, &Cura::SlicedObjectList::default_instance());
d->socket->registerMessageType(3, &Cura::Progress::default_instance());
d->socket->registerMessageType(4, &Cura::GCodeLayer::default_instance());
d->socket->registerMessageType(5, &Cura::ObjectPrintTime::default_instance());
d->socket->registerMessageType(6, &Cura::SettingList::default_instance());
d->socket->registerMessageType(7, &Cura::GCodePrefix::default_instance());
//private_data->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
private_data->socket->registerMessageType(&cura::proto::Slice::default_instance());
private_data->socket->registerMessageType(&cura::proto::SlicedObjectList::default_instance());
private_data->socket->registerMessageType(&cura::proto::Progress::default_instance());
private_data->socket->registerMessageType(&cura::proto::GCodeLayer::default_instance());
private_data->socket->registerMessageType(&cura::proto::ObjectPrintTime::default_instance());
private_data->socket->registerMessageType(&cura::proto::SettingList::default_instance());
private_data->socket->registerMessageType(&cura::proto::GCodePrefix::default_instance());
private_data->socket->registerMessageType(&cura::proto::SlicingFinished::default_instance());
d->socket->connect(ip, port);
private_data->socket->connect(ip, port);
log("Connecting to %s:%i\n", ip.c_str(), port);
while(private_data->socket->getState() != Arcus::SocketState::Connected && private_data->socket->getState() != Arcus::SocketState::Error)
while(d->socket->state() != Arcus::SocketState::Closed && d->socket->state() != Arcus::SocketState::Error)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
log("Connected to %s:%i\n", ip.c_str(), port);
bool slice_another_time = true;
// Start & continue listening as long as socket is not closed and there is no error.
while(private_data->socket->getState() != Arcus::SocketState::Closed && private_data->socket->getState() != Arcus::SocketState::Error && slice_another_time)
{
// Actually start handling messages.
Arcus::MessagePtr message = private_data->socket->takeNextMessage();
cura::proto::SettingList* setting_list = dynamic_cast<cura::proto::SettingList*>(message.get());
if(setting_list)
if(d->objectToSlice)
{
handleSettingList(setting_list);
}
/*cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
if(object_list)
{
handleObjectList(object_list);
}*/
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get());
if(slice)
{
// Reset object counts
private_data->object_count = 0;
private_data->object_ids.clear();
for(auto object : slice->object_lists())
{
handleObjectList(&object);
}
}
//If there is an object to slice, do so.
if(private_data->objects_to_slice.size())
{
FffProcessor::getInstance()->resetMeshGroupNumber();
for(auto object : private_data->objects_to_slice)
{
if(!FffProcessor::getInstance()->processMeshGroup(object.get()))
{
logError("Slicing mesh group failed!");
}
}
private_data->objects_to_slice.clear();
FffProcessor::getInstance()->finalize();
flushGcode();
sendPrintTime();
sendFinishedSlicing();
slice_another_time = false; // TODO: remove this when multiple slicing with CuraEngine is safe
//TODO: Support all-at-once/one-at-a-time printing
//private_data->processor->processModel(private_data->object_to_slice.get());
//private_data->object_to_slice.reset();
//private_data->processor->resetFileNumber();
d->processor->processModel(d->objectToSlice.get());
d->objectToSlice.reset();
d->processor->resetFileNumber();
//sendPrintTime();
sendPrintTime();
}
Arcus::MessagePtr message = d->socket->takeNextMessage();
Cura::SettingList* settingList = dynamic_cast<Cura::SettingList*>(message.get());
if(settingList)
{
handleSettingList(settingList);
}
Cura::ObjectList* objectList = dynamic_cast<Cura::ObjectList*>(message.get());
if(objectList)
{
handleObjectList(objectList);
}
std::this_thread::sleep_for(std::chrono::milliseconds(250));
if(!d->socket->errorString().empty()) {
logError("%s\n", d->socket->errorString().data());
d->socket->clearError();
}
}
log("Closing connection\n");
private_data->socket->close();
#endif
}
#ifdef ARCUS
void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
void CommandSocket::handleObjectList(Cura::ObjectList* list)
{
if(list->objects_size() <= 0)
{
return;
}
FMatrix3x3 matrix;
//private_data->object_count = 0;
//private_data->object_ids.clear();
private_data->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
MeshGroup* meshgroup = private_data->objects_to_slice.back().get();
for(auto setting : list->settings())
{
meshgroup->setSetting(setting.name(), setting.value());
}
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
{ // initialize remaining extruder trains and load the defaults
meshgroup->createExtruderTrain(extruder_nr)->setExtruderTrainDefaults(extruder_nr); // create new extruder train objects or use already existing ones
}
d->object_count = 0;
d->objectIds.clear();
d->objectToSlice = std::make_shared<PrintObject>(d->processor);
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;
d->objectToSlice->meshes.emplace_back(d->objectToSlice.get()); //Construct a new mesh and put it into printObject's mesh list.
Mesh& mesh = d->objectToSlice->meshes.back();
if(face_count <= 0)
{
logWarning("Got an empty mesh, ignoring it!");
continue;
}
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("solid Cura_out\n");
int extruder_train_nr = 0; // TODO: make primary extruder configurable!
for(auto setting : object.settings())
{
if (setting.name() == "extruder_nr")
{
extruder_train_nr = std::stoi(setting.value());
break;
}
}
SettingsBase* extruder_train = meshgroup->getExtruderTrain(extruder_train_nr);
meshgroup->meshes.push_back(new Mesh(extruder_train)); //Construct a new mesh (with the corresponding extruder train as settings parent object) and put it into MeshGroup's mesh list.
Mesh& mesh = *meshgroup->meshes.back();
for(int i = 0; i < face_count; ++i)
int bytesPerFace = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
int faceCount = object.vertices().size() / bytesPerFace;
for(int i = 0; i < faceCount; ++i)
{
//TODO: Apply matrix
std::string data = object.vertices().substr(i * bytes_per_face, bytes_per_face);
const FPoint3* float_vertices = reinterpret_cast<const FPoint3*>(data.data());
std::string data = object.vertices().substr(i * bytesPerFace, bytesPerFace);
const FPoint3* floatVerts = reinterpret_cast<const FPoint3*>(data.data());
Point3 verts[3];
verts[0] = matrix.apply(float_vertices[0]);
verts[1] = matrix.apply(float_vertices[1]);
verts[2] = matrix.apply(float_vertices[2]);
verts[0] = matrix.apply(floatVerts[0]);
verts[1] = matrix.apply(floatVerts[1]);
verts[2] = matrix.apply(floatVerts[2]);
mesh.addFace(verts[0], verts[1], verts[2]);
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" facet normal -1 0 0\n");
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" outer loop\n");
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" vertex "<<INT2MM(verts[0].x) <<" " << INT2MM(verts[0].y) <<" " << INT2MM(verts[0].z) << "\n");
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" vertex "<<INT2MM(verts[1].x) <<" " << INT2MM(verts[1].y) <<" " << INT2MM(verts[1].z) << "\n");
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" vertex "<<INT2MM(verts[2].x) <<" " << INT2MM(verts[2].y) <<" " << INT2MM(verts[2].z) << "\n");
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" endloop\n");
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" endfacet\n");
}
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("endsolid Cura_out\n");
for(auto setting : object.settings())
{
mesh.setSetting(setting.name(), setting.value());
}
private_data->object_ids.push_back(object.id());
d->objectIds.push_back(object.id());
mesh.finish();
}
private_data->object_count++;
meshgroup->finalize();
d->object_count++;
d->objectToSlice->finalize();
}
void CommandSocket::handleSettingList(cura::proto::SettingList* list)
void CommandSocket::handleSettingList(Cura::SettingList* list)
{
for(auto setting : list->settings())
{
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
d->processor->setSetting(setting.name(), setting.value());
}
}
#endif
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
{
#ifdef ARCUS
if(!private_data->current_sliced_object)
if(!d->currentSlicedObject)
{
return;
}
cura::proto::Layer* layer = private_data->getLayerById(layer_nr);
Cura::Layer* layer = d->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)
void CommandSocket::sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
{
#ifdef ARCUS
if(!private_data->current_sliced_object)
if(!d->currentSlicedObject)
return;
if (polygons.size() == 0)
return;
cura::proto::Layer* proto_layer = private_data->getLayerById(layer_nr);
Cura::Layer* layer = d->getLayerById(layer_nr);
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));
Cura::Polygon* p = layer->add_polygons();
p->set_type(static_cast<Cura::Polygon_Type>(type));
std::string polydata;
polydata.append(reinterpret_cast<const char*>(polygons[i].data()), polygons[i].size() * sizeof(Point));
p->set_points(polydata);
p->set_line_width(line_width);
}
#endif
}
void CommandSocket::sendProgress(float amount)
{
#ifdef ARCUS
auto message = std::make_shared<cura::proto::Progress>();
amount /= private_data->object_count;
amount += private_data->sliced_objects * (1. / private_data->object_count);
auto message = std::make_shared<Cura::Progress>();
message->set_amount(amount);
private_data->socket->sendMessage(message);
#endif
}
void CommandSocket::sendProgressStage(Progress::Stage stage)
{
// TODO
d->socket->sendMessage(message);
}
void CommandSocket::sendPrintTime()
{
#ifdef ARCUS
auto message = std::make_shared<cura::proto::ObjectPrintTime>();
message->set_time(FffProcessor::getInstance()->getTotalPrintTime());
message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(0));
private_data->socket->sendMessage(message);
#endif
auto message = std::make_shared<Cura::ObjectPrintTime>();
message->set_time(d->processor->getTotalPrintTime());
message->set_material_amount(d->processor->getTotalFilamentUsed(0));
d->socket->sendMessage(message);
}
void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float print_time)
@@ -381,97 +210,65 @@ void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float
void CommandSocket::beginSendSlicedObject()
{
#ifdef ARCUS
if(!private_data->sliced_object_list)
if(!d->slicedObjectList)
{
private_data->sliced_object_list = std::make_shared<cura::proto::SlicedObjectList>();
d->slicedObjectList = std::make_shared<Cura::SlicedObjectList>();
}
private_data->current_sliced_object = private_data->sliced_object_list->add_objects();
private_data->current_sliced_object->set_id(private_data->object_ids[private_data->sliced_objects]);
#endif
d->currentSlicedObject = d->slicedObjectList->add_objects();
d->currentSlicedObject->set_id(d->objectIds[d->slicedObjects]);
}
void CommandSocket::endSendSlicedObject()
{
#ifdef ARCUS
private_data->sliced_objects++;
private_data->current_layer_offset = private_data->current_layer_count;
std::cout << "End sliced object called. Sliced objects " << private_data->sliced_objects << " object count: " << private_data->object_count << std::endl;
if(private_data->sliced_objects >= private_data->object_count)
d->slicedObjects++;
if(d->slicedObjects >= d->object_count)
{
private_data->socket->sendMessage(private_data->sliced_object_list);
private_data->sliced_objects = 0;
private_data->current_layer_count = 0;
private_data->current_layer_offset = 0;
private_data->sliced_object_list.reset();
private_data->current_sliced_object = nullptr;
private_data->sliced_layers.clear();
auto done_message = std::make_shared<cura::proto::SlicingFinished>();
private_data->socket->sendMessage(done_message);
d->socket->sendMessage(d->slicedObjectList);
d->slicedObjects = 0;
d->slicedObjectList.reset();
d->currentSlicedObject = nullptr;
}
#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
d->processor->setTargetStream(&d->gcode_output_stream);
}
void CommandSocket::flushGcode()
void CommandSocket::sendGCodeLayer()
{
#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);
auto message = std::make_shared<Cura::GCodeLayer>();
message->set_id(d->objectIds[0]);
message->set_data(d->gcode_output_stream.str());
d->socket->sendMessage(message);
private_data->gcode_output_stream.str("");
#endif
d->gcode_output_stream.str("");
}
void CommandSocket::sendGCodePrefix(std::string prefix)
{
#ifdef ARCUS
auto message = std::make_shared<cura::proto::GCodePrefix>();
auto message = std::make_shared<Cura::GCodePrefix>();
message->set_data(prefix);
private_data->socket->sendMessage(message);
#endif
d->socket->sendMessage(message);
}
#ifdef ARCUS
cura::proto::Layer* CommandSocket::Private::getLayerById(int id)
Cura::Layer* CommandSocket::Private::getLayerById(int id)
{
id += current_layer_offset;
auto itr = std::find_if(currentSlicedObject->mutable_layers()->begin(), currentSlicedObject->mutable_layers()->end(), [id](Cura::Layer& l) { return l.id() == id; });
auto itr = sliced_layers.find(id);
cura::proto::Layer* layer = nullptr;
if(itr != sliced_layers.end())
Cura::Layer* layer = nullptr;
if(itr != currentSlicedObject->mutable_layers()->end())
{
layer = itr->second;
layer = &(*itr);
}
else
{
layer = current_sliced_object->add_layers();
layer = currentSlicedObject->add_layers();
layer->set_id(id);
current_layer_count++;
sliced_layers[id] = layer;
}
return layer;
}
#endif
}//namespace cura
+8 -88
Ver Arquivo
@@ -4,120 +4,40 @@
#include "utils/socket.h"
#include "utils/polygon.h"
#include "settings.h"
#include "Progress.h"
#include "PrintFeature.h"
#include <memory>
#ifdef ARCUS
#include "Cura.pb.h"
#endif
namespace cura
{
namespace cura {
class fffProcessor;
class CommandSocket
{
private:
static CommandSocket* instance; //!< May be a nullptr in case it hasn't been instantiated.
CommandSocket(); //!< The single constructor is known only privately, since this class is similar to a singleton class (except the single object doesn't need to be instantiated)
public:
static CommandSocket* getInstance(); //!< Get the CommandSocket instance, or nullptr if it hasn't been instantiated.
CommandSocket(fffProcessor* processor);
static void instantiate(); //!< Instantiate the CommandSocket.
static bool isInstantiated(); //!< Check whether the singleton is instantiated
/*!
* Connect with the GUI
* This creates and initialises the arcus socket and then continues listening for messages.
* \param ip string containing the ip to connect with
* \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
*/
void handleObjectList(cura::proto::ObjectList* list);
void handleObjectList(Cura::ObjectList* list);
void handleSettingList(Cura::SettingList* list);
/*!
* Handler for SettingList message.
* This simply sets all the settings by using key value pair
*/
void handleSettingList(cura::proto::SettingList* list);
#endif
/*!
* Send info on a layer to be displayed by the forntend: set the z and the thickness of the layer.
*/
void sendLayerInfo(int layer_nr, int32_t z, int32_t height);
/*!
* Send a polygon to the engine. This is used for the layerview in the GUI
*/
void sendPolygons(cura::PrintFeatureType type, int layer_nr, cura::Polygons& polygons, int line_width);
/*!
* Send a polygon to the engine if the command socket is instantiated. This is used for the layerview in the GUI
*/
static void sendPolygonsToCommandSocket(cura::PrintFeatureType type, int layer_nr, cura::Polygons& polygons, int line_width);
/*!
* Send progress to GUI
*/
void sendPolygons(cura::PolygonType type, int layer_nr, cura::Polygons& polygons, int line_width);
void sendProgress(float amount);
/*!
* Send the current stage of the process to the GUI (starting, slicing infill, etc)
*/
void sendProgressStage(Progress::Stage stage);
/*!
* Send time estimate of how long print would take.
*/
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();
/*!
* Conclude the slicing of the current meshgroup, so that we can start the next
*/
void endSendSlicedObject();
/*!
* \brief Sends a message to indicate that all the slicing is done.
*
* This should indicate that no more data (g-code, prefix/postfix, metadata
* or otherwise) should be sent any more regarding the latest slice job.
*/
void sendFinishedSlicing();
void beginGCode();
/*!
* Flush the gcode in gcode_output_stream into a message queued in the socket.
*/
void flushGcode();
void sendGCodeLayer();
void sendGCodePrefix(std::string prefix);
#ifdef ARCUS
private:
class Private;
const std::unique_ptr<Private> private_data;
#endif
const std::unique_ptr<Private> d;
};
}//namespace cura
+1 -1
Ver Arquivo
@@ -14,7 +14,7 @@
#define DEBUG 1
#define DEBUG_SHOW_LINE 1
#define DEBUG_SHOW_LINE 0
#if DEBUG_SHOW_LINE == 1
#define DEBUG_FILE_LINE __FILE_NAME__ << "." << __LINE__ << ": "
+1188
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+213 -377
Ver Arquivo
@@ -1,81 +1,79 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include <stdarg.h>
#include <iomanip>
#include <cmath>
#include "gcodeExport.h"
#include "utils/logoutput.h"
#include "PrintFeature.h"
namespace cura {
GCodeExport::GCodeExport()
: output_stream(&std::cout)
, currentPosition(0,0,MM2INT(20))
, layer_nr(0)
: output_stream(&std::cout), currentPosition(0,0,0), startPosition(INT32_MIN,INT32_MIN,0)
{
current_e_value = 0;
extrusion_amount = 0;
retraction_extrusion_window = 0.0;
extruderSwitchRetraction = 14.5;
current_extruder = 0;
currentFanSpeed = -1;
totalPrintTime = 0.0;
for(unsigned int e=0; e<MAX_EXTRUDERS; e++)
{
totalFilament[e] = 0.0;
currentTemperature[e] = 0;
filament_diameter[e] = 0;
}
currentSpeed = 1;
isZHopped = 0;
setFlavor(EGCodeFlavor::REPRAP);
retractionPrimeSpeed = 1;
isRetracted = false;
isZHopped = false;
setFlavor(GCODE_FLAVOR_REPRAP);
memset(extruderOffset, 0, sizeof(extruderOffset));
}
GCodeExport::~GCodeExport()
{
}
void GCodeExport::setLayerNr(unsigned int layer_nr_) {
layer_nr = layer_nr_;
}
void GCodeExport::setOutputStream(std::ostream* stream)
{
output_stream = stream;
*output_stream << std::fixed;
}
void GCodeExport::setExtruderOffset(int id, Point p)
{
extruderOffset[id] = p;
}
Point GCodeExport::getExtruderOffset(int id)
{
return extruder_attr[id].nozzle_offset;
return extruderOffset[id];
}
Point GCodeExport::getGcodePos(int64_t x, int64_t y, int extruder_train)
void GCodeExport::setSwitchExtruderCode(int id, std::string preSwitchExtruderCode, std::string postSwitchExtruderCode)
{
if (use_extruder_offset_to_offset_coords) { return Point(x,y) - getExtruderOffset(extruder_train); }
else { return Point(x,y); }
this->preSwitchExtruderCode[id] = preSwitchExtruderCode;
this->postSwitchExtruderCode[id] = postSwitchExtruderCode;
}
void GCodeExport::setFlavor(EGCodeFlavor flavor)
{
this->flavor = flavor;
if (flavor == EGCodeFlavor::MACH3)
if (flavor == GCODE_FLAVOR_MACH3)
for(int n=0; n<MAX_EXTRUDERS; n++)
extruder_attr[n].extruderCharacter = 'A' + n;
extruderCharacter[n] = 'A' + n;
else
for(int n=0; n<MAX_EXTRUDERS; n++)
extruder_attr[n].extruderCharacter = 'E';
if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC)
extruderCharacter[n] = 'E';
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
{
is_volumatric = true;
}
else
{
is_volumatric = false;
}
if (flavor == EGCodeFlavor::BFB || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC || flavor == EGCodeFlavor::ULTIGCODE)
{
firmware_retract = true;
}
}
else
{
firmware_retract = false;
is_volumatric = false;
}
}
@@ -84,6 +82,15 @@ EGCodeFlavor GCodeExport::getFlavor()
return this->flavor;
}
void GCodeExport::setRetractionSettings(int extruderSwitchRetraction, int extruderSwitchRetractionSpeed, int extruderSwitchPrimeSpeed, int retraction_extrusion_window, int retraction_count_max)
{
this->extruderSwitchRetraction = INT2MM(extruderSwitchRetraction);
this->extruderSwitchRetractionSpeed = extruderSwitchRetractionSpeed;
this->extruderSwitchPrimeSpeed = extruderSwitchPrimeSpeed;
this->retraction_extrusion_window = INT2MM(retraction_extrusion_window);
this->retraction_count_max = retraction_count_max;
}
void GCodeExport::setZ(int z)
{
this->zPos = z;
@@ -103,42 +110,52 @@ int GCodeExport::getPositionZ()
return currentPosition.z;
}
void GCodeExport::resetStartPosition()
{
startPosition.x = INT32_MIN;
startPosition.y = INT32_MIN;
}
Point GCodeExport::getStartPositionXY()
{
return Point(startPosition.x, startPosition.y);
}
int GCodeExport::getExtruderNr()
{
return current_extruder;
}
void GCodeExport::setFilamentDiameter(unsigned int extruder, int diameter)
double GCodeExport::getFilamentArea(unsigned int extruder)
{
double r = INT2MM(diameter) / 2.0;
double area = M_PI * r * r;
extruder_attr[extruder].filament_area = area;
double r = INT2MM(filament_diameter[extruder]) / 2.0;
double filament_area = M_PI * r * r;
return filament_area;
}
double GCodeExport::getCurrentExtrudedVolume()
void GCodeExport::setFilamentDiameter(unsigned int n, int diameter)
{
filament_diameter[n] = diameter;
}
double GCodeExport::getExtrusionAmountMM3(unsigned int extruder)
{
double extrusion_amount = current_e_value;
if (!firmware_retract)
{ // no E values are changed to perform a retraction
extrusion_amount -= extruder_attr[current_extruder].retraction_e_amount_at_e_start; // subtract the increment in E which was used for the first unretraction instead of extrusion
extrusion_amount += extruder_attr[current_extruder].retraction_e_amount_current; // add the decrement in E which the filament is behind on extrusion due to the last retraction
}
if (is_volumatric)
{
return extrusion_amount;
}
else
{
return extrusion_amount * extruder_attr[current_extruder].filament_area;
return extrusion_amount * getFilamentArea(extruder);
}
}
double GCodeExport::getTotalFilamentUsed(int extruder_nr)
double GCodeExport::getTotalFilamentUsed(int e)
{
if (extruder_nr == current_extruder)
return extruder_attr[extruder_nr].totalFilament + getCurrentExtrudedVolume();
return extruder_attr[extruder_nr].totalFilament;
if (e == current_extruder)
return totalFilament[e] + getExtrusionAmountMM3(e);
return totalFilament[e];
}
double GCodeExport::getTotalPrintTime()
@@ -151,10 +168,10 @@ void GCodeExport::resetTotalPrintTimeAndFilament()
totalPrintTime = 0;
for(unsigned int e=0; e<MAX_EXTRUDERS; e++)
{
extruder_attr[e].totalFilament = 0.0;
extruder_attr[e].currentTemperature = 0;
totalFilament[e] = 0.0;
currentTemperature[e] = 0;
}
current_e_value = 0.0;
extrusion_amount = 0.0;
estimateCalculator.reset();
}
@@ -166,68 +183,18 @@ void GCodeExport::updateTotalPrintTime()
void GCodeExport::writeComment(std::string comment)
{
*output_stream << ";";
for (unsigned int i = 0; i < comment.length(); i++)
{
if (comment[i] == '\n')
{
*output_stream << "\\n";
}else{
*output_stream << comment[i];
}
}
*output_stream << "\n";
*output_stream << ";" << comment << "\n";
}
void GCodeExport::writeTypeComment(const char* type)
{
*output_stream << ";TYPE:" << type << "\n";
}
void GCodeExport::writeTypeComment(PrintFeatureType type)
{
switch (type)
{
case PrintFeatureType::OuterWall:
*output_stream << ";TYPE:WALL-OUTER\n";
break;
case PrintFeatureType::InnerWall:
*output_stream << ";TYPE:WALL-INNER\n";
break;
case PrintFeatureType::Skin:
*output_stream << ";TYPE:SKIN\n";
break;
case PrintFeatureType::Support:
*output_stream << ";TYPE:SUPPORT\n";
break;
case PrintFeatureType::Skirt:
*output_stream << ";TYPE:SKIRT\n";
break;
case PrintFeatureType::Infill:
*output_stream << ";TYPE:FILL\n";
break;
case PrintFeatureType::SupportInfill:
*output_stream << ";TYPE:SUPPORT\n";
break;
case PrintFeatureType::MoveCombing:
case PrintFeatureType::MoveRetraction:
default:
// do nothing
break;
}
}
void GCodeExport::writeLayerComment(int layer_nr)
{
*output_stream << ";LAYER:" << layer_nr << "\n";
}
void GCodeExport::writeLayerCountComment(int layer_count)
{
*output_stream << ";LAYER_COUNT:" << layer_count << "\n";
}
void GCodeExport::writeLine(const char* line)
{
*output_stream << line << "\n";
@@ -235,336 +202,206 @@ void GCodeExport::writeLine(const char* line)
void GCodeExport::resetExtrusionValue()
{
if (current_e_value != 0.0 && flavor != EGCodeFlavor::MAKERBOT && flavor != EGCodeFlavor::BFB)
if (extrusion_amount != 0.0 && flavor != GCODE_FLAVOR_MAKERBOT && flavor != GCODE_FLAVOR_BFB)
{
*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)
{ // update the extruded_volume_at_previous_n_retractions only of the current extruder, since other extruders don't extrude the current volume
extruded_volume_at_retraction -= current_extruded_volume;
}
current_e_value = 0.0;
extruder_attr[current_extruder].retraction_e_amount_at_e_start = extruder_attr[current_extruder].retraction_e_amount_current;
*output_stream << "G92 " << extruderCharacter[current_extruder] << "0\n";
totalFilament[current_extruder] += getExtrusionAmountMM3(current_extruder);
for (unsigned int i = 0; i < extrusion_amount_at_previous_n_retractions.size(); i++)
extrusion_amount_at_previous_n_retractions[i] -= extrusion_amount;
extrusion_amount = 0.0;
}
}
void GCodeExport::writeDelay(double timeAmount)
{
*output_stream << "G4 P" << int(timeAmount * 1000) << "\n";
estimateCalculator.addTime(timeAmount);
totalPrintTime += timeAmount;
}
void GCodeExport::writeMove(Point p, double speed, double extrusion_mm3_per_mm)
void GCodeExport::writeMove(Point p, int speed, double extrusion_mm3_per_mm)
{
writeMove(p.X, p.Y, zPos, speed, extrusion_mm3_per_mm);
}
void GCodeExport::writeMove(Point3 p, double speed, double extrusion_mm3_per_mm)
void GCodeExport::writeMove(Point3 p, int speed, double extrusion_mm3_per_mm)
{
writeMove(p.x, p.y, p.z, speed, extrusion_mm3_per_mm);
}
void GCodeExport::writeMoveBFB(int x, int y, int z, double speed, double extrusion_mm3_per_mm)
{
double extrusion_per_mm = extrusion_mm3_per_mm;
if (!is_volumatric)
{
extrusion_per_mm = extrusion_mm3_per_mm / extruder_attr[current_extruder].filament_area;
}
Point gcode_pos = getGcodePos(x,y, current_extruder);
//For Bits From Bytes machines, we need to handle this completely differently. As they do not use E values but RPM values.
float fspeed = speed * 60;
float rpm = extrusion_per_mm * speed * 60;
const float mm_per_rpm = 4.0; //All BFB machines have 4mm per RPM extrusion.
rpm /= mm_per_rpm;
if (rpm > 0)
{
if (extruder_attr[current_extruder].retraction_e_amount_current)
{
if (currentSpeed != double(rpm))
{
//fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed);
//fprintf(f, "M108 S%0.1f\r\n", rpm);
*output_stream << "M108 S" << std::setprecision(1) << rpm << "\r\n";
currentSpeed = double(rpm);
}
//Add M101 or M201 to enable the proper extruder.
*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.
// (Trick copied from KISSlicer, thanks Jonathan)
fspeed *= (rpm / (roundf(rpm * 100) / 100));
//Increase the extrusion amount to calculate the amount of filament used.
Point3 diff = Point3(x,y,z) - getPosition();
current_e_value += extrusion_per_mm * diff.vSizeMM();
}
else
{
//If we are not extruding, check if we still need to disable the extruder. This causes a retraction due to auto-retraction.
if (!extruder_attr[current_extruder].retraction_e_amount_current)
{
*output_stream << "M103\r\n";
extruder_attr[current_extruder].retraction_e_amount_current = 1.0; // 1.0 used as stub; BFB doesn't use the actual retraction amount; it performs retraction on the firmware automatically
}
}
*output_stream << std::setprecision(3) <<
"G1 X" << INT2MM(gcode_pos.X) <<
" Y" << INT2MM(gcode_pos.Y) <<
" Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n";
currentPosition = Point3(x, y, z);
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)
void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3_per_mm)
{
if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z)
return;
#ifdef ASSERT_INSANE_OUTPUT
assert(speed < 200 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release)
assert(currentPosition != no_point3);
assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release)
#endif //ASSERT_INSANE_OUTPUT
if (extrusion_mm3_per_mm < 0)
logWarning("Warning! Negative extrusion move!");
if (flavor == EGCodeFlavor::BFB)
{
writeMoveBFB(x, y, z, speed, extrusion_mm3_per_mm);
return;
}
double extrusion_per_mm = extrusion_mm3_per_mm;
if (!is_volumatric)
{
extrusion_per_mm = extrusion_mm3_per_mm / extruder_attr[current_extruder].filament_area;
extrusion_per_mm = extrusion_mm3_per_mm / getFilamentArea(current_extruder);
}
Point gcode_pos = getGcodePos(x,y, current_extruder);
if (extrusion_mm3_per_mm > 0.000001)
if (flavor == GCODE_FLAVOR_BFB)
{
Point3 diff = Point3(x,y,z) - getPosition();
if (isZHopped > 0)
//For Bits From Bytes machines, we need to handle this completely differently. As they do not use E values but RPM values.
float fspeed = speed * 60;
float rpm = extrusion_per_mm * speed * 60;
const float mm_per_rpm = 4.0; //All BFB machines have 4mm per RPM extrusion.
rpm /= mm_per_rpm;
if (rpm > 0)
{
*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 += (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\n";
//Assume default UM2 retraction settings.
if (prime_volume > 0)
if (isRetracted)
{
if (currentSpeed != int(rpm * 10))
{
*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;
//fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed);
//fprintf(f, "M108 S%0.1f\r\n", rpm);
*output_stream << "M108 S" << std::setprecision(1) << rpm << "\r\n";
currentSpeed = int(rpm * 10);
}
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), 25.0);
//Add M101 or M201 to enable the proper extruder.
*output_stream << "M" << int((current_extruder + 1) * 100 + 1) << "\r\n";
isRetracted = false;
}
else
//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.
// (Trick copied from KISSlicer, thanks Jonathan)
fspeed *= (rpm / (roundf(rpm * 100) / 100));
//Increase the extrusion amount to calculate the amount of filament used.
Point3 diff = Point3(x,y,z) - getPosition();
extrusion_amount += extrusion_per_mm * diff.vSizeMM();
}else{
//If we are not extruding, check if we still need to disable the extruder. This causes a retraction due to auto-retraction.
if (!isRetracted)
{
current_e_value += extruder_attr[current_extruder].retraction_e_amount_current;
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
*output_stream << "M103\r\n";
isRetracted = true;
}
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.
}
*output_stream << std::setprecision(3) << "G1 X" << INT2MM(x - extruderOffset[current_extruder].X) << " Y" << INT2MM(y - extruderOffset[current_extruder].Y) << " Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n";
}else{
//Normal E handling.
if (extrusion_mm3_per_mm > 0.000001)
{
Point3 diff = Point3(x,y,z) - getPosition();
if (isZHopped > 0)
{
resetExtrusionValue();
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << "\n";
isZHopped = false;
}
extruder_attr[current_extruder].retraction_e_amount_current = 0.0;
if (isRetracted)
{
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
{
*output_stream << "G11\n";
//Assume default UM2 retraction settings.
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), 25.0);
}else{
*output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruderCharacter[current_extruder] << std::setprecision(5) << extrusion_amount << "\n";
currentSpeed = retractionPrimeSpeed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), currentSpeed);
}
if (getExtrusionAmountMM3(current_extruder) > 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.
resetExtrusionValue();
isRetracted = false;
}
extrusion_amount += extrusion_per_mm * diff.vSizeMM();
*output_stream << "G1";
}else{
*output_stream << "G0";
}
else if (prime_volume > 0.0)
if (currentSpeed != speed)
{
*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), current_e_value), currentSpeed);
*output_stream << " F" << (speed * 60);
currentSpeed = speed;
}
extruder_attr[current_extruder].prime_volume = 0.0;
current_e_value += extrusion_per_mm * diff.vSizeMM();
*output_stream << "G1";
}
else
{
*output_stream << "G0";
if (CommandSocket::isInstantiated())
{
// we should send this travel as a non-retraction move
cura::Polygons travelPoly;
PolygonRef travel = travelPoly.newPoly();
travel.add(Point(currentPosition.x, currentPosition.y));
travel.add(Point(x, y));
CommandSocket::getInstance()->sendPolygons(extruder_attr[current_extruder].retraction_e_amount_current ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing, layer_nr, travelPoly, extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1));
}
*output_stream << std::setprecision(3) << " X" << INT2MM(x - extruderOffset[current_extruder].X) << " Y" << INT2MM(y - extruderOffset[current_extruder].Y);
if (z != currentPosition.z)
*output_stream << " Z" << INT2MM(z);
if (extrusion_mm3_per_mm > 0.000001)
*output_stream << " " << extruderCharacter[current_extruder] << std::setprecision(5) << extrusion_amount;
*output_stream << "\n";
}
if (currentSpeed != speed)
{
*output_stream << " F" << (speed * 60);
currentSpeed = speed;
}
*output_stream << std::setprecision(3) <<
" X" << INT2MM(gcode_pos.X) <<
" Y" << INT2MM(gcode_pos.Y);
if (z != currentPosition.z + isZHopped)
*output_stream << " Z" << INT2MM(z + isZHopped);
if (extrusion_mm3_per_mm > 0.000001)
*output_stream << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value;
*output_stream << "\n";
currentPosition = Point3(x, y, z);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), speed);
startPosition = currentPosition;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), speed);
}
void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
{
if (flavor == EGCodeFlavor::BFB)//BitsFromBytes does automatic retraction.
{
if (flavor == GCODE_FLAVOR_BFB)//BitsFromBytes does automatic retraction.
return;
}
if (extruder_attr[current_extruder].retraction_e_amount_current == config->distance * ((is_volumatric)? extruder_attr[current_extruder].filament_area : 1.0))
{
if (isRetracted)
return;
}
if (config->distance <= 0)
{
if (config->amount <= 0)
return;
if (!force && retraction_count_max > 0 && int(extrusion_amount_at_previous_n_retractions.size()) == retraction_count_max - 1
&& extrusion_amount < extrusion_amount_at_previous_n_retractions.back() + retraction_extrusion_window)
return;
}
{ // handle retraction limitation
double current_extruded_volume = getCurrentExtrudedVolume();
std::deque<double>& extruded_volume_at_previous_n_retractions = extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions;
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
extruded_volume_at_previous_n_retractions.pop_back();
}
if (!force && config->retraction_count_max <= 0)
{
return;
}
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config->retraction_extrusion_window * extruder_attr[current_extruder].filament_area)
{
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)
{
extruded_volume_at_previous_n_retractions.pop_back();
}
}
if (config->primeAmount > 0)
extrusion_amount += config->primeAmount;
retractionPrimeSpeed = config->primeSpeed;
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 (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
{
*output_stream << "G10\n";
//Assume default UM2 retraction settings.
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value - retraction_e_amount), 25); // TODO: hardcoded values!
}
else
{
current_e_value -= retraction_e_amount;
*output_stream << "G1 F" << (config->speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
double retraction_distance = 4.5;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount - retraction_distance), 25); // TODO: hardcoded values!
}else{
*output_stream << "G1 F" << (config->speed * 60) << " " << extruderCharacter[current_extruder] << std::setprecision(5) << extrusion_amount - config->amount << "\n";
currentSpeed = config->speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount - config->amount), currentSpeed);
}
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 = config->zHop;
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << "\n";
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + config->zHop) << "\n";
isZHopped = true;
}
extrusion_amount_at_previous_n_retractions.push_front(extrusion_amount);
if (int(extrusion_amount_at_previous_n_retractions.size()) == retraction_count_max)
{
extrusion_amount_at_previous_n_retractions.pop_back();
}
isRetracted = true;
}
void GCodeExport::writeRetraction_extruderSwitch()
void GCodeExport::switchExtruder(int newExtruder)
{
if (flavor == EGCodeFlavor::BFB)
if (current_extruder == newExtruder)
return;
if (flavor == GCODE_FLAVOR_BFB)
{
if (!extruder_attr[current_extruder].retraction_e_amount_current)
if (!isRetracted)
*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
isRetracted = true;
return;
}
double retraction_e_amount = extruder_attr[current_extruder].extruder_switch_retraction_distance * ((is_volumatric)? extruder_attr[current_extruder].filament_area : 1.0);
if (extruder_attr[current_extruder].retraction_e_amount_current == retraction_e_amount)
{
return;
}
double current_extruded_volume = getCurrentExtrudedVolume();
std::deque<double>& extruded_volume_at_previous_n_retractions = extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions;
extruded_volume_at_previous_n_retractions.push_front(current_extruded_volume);
if (firmware_retract)
{
if (extruder_attr[current_extruder].retraction_e_amount_current)
{
return;
}
*output_stream << "G10 S1\n";
}
else
{
current_e_value -= retraction_e_amount;
*output_stream << "G1 F" << (extruder_attr[current_extruder].extruderSwitchRetractionSpeed * 60) << " "
<< extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
// the E value of the extruder switch retraction 'overwrites' the E value of the normal retraction
currentSpeed = extruder_attr[current_extruder].extruderSwitchRetractionSpeed;
extruder_attr[current_extruder].last_retraction_prime_speed = extruder_attr[current_extruder].extruderSwitchPrimeSpeed;
}
extruder_attr[current_extruder].retraction_e_amount_current = retraction_e_amount; // suppose that for UM2 the retraction amount in the firmware is equal to the provided amount
}
void GCodeExport::switchExtruder(int new_extruder)
{
if (current_extruder == new_extruder)
return;
writeRetraction_extruderSwitch();
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());
if (flavor == EGCodeFlavor::MAKERBOT)
resetExtrusionValue();
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
{
*output_stream << "G10 S1\n";
}else{
*output_stream << "G1 F" << (extruderSwitchRetractionSpeed * 60) << " " << extruderCharacter[current_extruder] << std::setprecision(5) << (extrusion_amount - extruderSwitchRetraction) << "\n";
currentSpeed = extruderSwitchRetractionSpeed;
}
current_extruder = newExtruder;
if (flavor == GCODE_FLAVOR_MACH3)
resetExtrusionValue();
isRetracted = true;
writeCode(preSwitchExtruderCode[current_extruder].c_str());
if (flavor == GCODE_FLAVOR_MAKERBOT)
*output_stream << "M135 T" << current_extruder << "\n";
}
else
{
*output_stream << "T" << current_extruder << "\n";
}
writeCode(extruder_attr[new_extruder].start_code.c_str());
writeCode(postSwitchExtruderCode[current_extruder].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;
@@ -573,26 +410,26 @@ void GCodeExport::switchExtruder(int new_extruder)
void GCodeExport::writeCode(const char* str)
{
*output_stream << str;
if (flavor == EGCodeFlavor::BFB)
if (flavor == GCODE_FLAVOR_BFB)
*output_stream << "\r\n";
else
*output_stream << "\n";
}
void GCodeExport::writeFanCommand(double speed)
void GCodeExport::writeFanCommand(int speed)
{
if (currentFanSpeed == speed)
return;
if (speed > 0)
{
if (flavor == EGCodeFlavor::MAKERBOT)
if (flavor == GCODE_FLAVOR_MAKERBOT)
*output_stream << "M126 T0\n"; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
else
*output_stream << "M106 S" << (speed * 255 / 100) << "\n";
}
else
{
if (flavor == EGCodeFlavor::MAKERBOT)
if (flavor == GCODE_FLAVOR_MAKERBOT)
*output_stream << "M127 T0\n";
else
*output_stream << "M107\n";
@@ -600,9 +437,9 @@ void GCodeExport::writeFanCommand(double speed)
currentFanSpeed = speed;
}
void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool wait)
void GCodeExport::writeTemperatureCommand(int extruder, int temperature, bool wait)
{
if (!wait && extruder_attr[extruder].currentTemperature == temperature)
if (!wait && currentTemperature[extruder] == temperature)
return;
if (wait)
@@ -612,10 +449,10 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
if (extruder != current_extruder)
*output_stream << " T" << extruder;
*output_stream << " S" << temperature << "\n";
extruder_attr[extruder].currentTemperature = temperature;
currentTemperature[extruder] = temperature;
}
void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
void GCodeExport::writeBedTemperatureCommand(int temperature, bool wait)
{
if (wait)
*output_stream << "M190 S";
@@ -624,15 +461,15 @@ void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
*output_stream << temperature << "\n";
}
void GCodeExport::finalize(double moveSpeed, const char* endCode)
void GCodeExport::finalize(int maxObjectHeight, int moveSpeed, const char* endCode)
{
std::cerr << "maxObjectHeight : " << maxObjectHeight << std::endl;
writeFanCommand(0);
setZ(maxObjectHeight + 5000);
writeMove(Point3(0,0,maxObjectHeight + 5000) + getPositionXY(), moveSpeed, 0);
writeCode(endCode);
int 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)));
@@ -640,4 +477,3 @@ void GCodeExport::finalize(double moveSpeed, const char* endCode)
}
}//namespace cura
+77 -198
Ver Arquivo
@@ -4,123 +4,83 @@
#include <stdio.h>
#include <deque> // for extrusionAmountAtPreviousRetractions
#include <sstream> // for stream.str()
#include "settings.h"
#include "utils/intpoint.h"
#include "utils/NoCopy.h"
#include "timeEstimate.h"
#include "MeshGroup.h"
#include "commandSocket.h"
namespace cura {
struct CoastingConfig
{
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;
double amount; //!< The amount
int speed;
int primeSpeed;
double primeAmount;
int zHop;
};
//The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
class GCodePathConfig
{
private:
double speed_base; //!< movement speed (mm/s) specific to this print feature
double speed_current; //!< current movement speed (mm/s) (modified by layer_nr etc.)
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
int speed;
int line_width;
int flow;
int layer_thickness;
double extrusion_mm3_per_mm;
public:
PrintFeatureType type; //!< name of the feature type
const char* name;
bool spiralize;
RetractionConfig *const retraction_config;
RetractionConfig* retraction_config;
// GCodePathConfig() : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(nullptr), spiralize(false), retraction_config(nullptr) {}
GCodePathConfig(RetractionConfig* retraction_config, PrintFeatureType type) : speed_base(0), speed_current(0), line_width(0), extrusion_mm3_per_mm(0.0), type(type), spiralize(false), retraction_config(retraction_config) {}
GCodePathConfig() : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(nullptr), spiralize(false), retraction_config(nullptr) {}
GCodePathConfig(RetractionConfig* retraction_config, const char* name) : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(name), 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)
void setSpeed(int speed)
{
speed_base = speed;
this->speed_current = speed;
this->line_width = line_width;
this->flow = flow;
this->speed = speed;
}
/*!
* Set the layer height and (re)compute the extrusion_per_mm
*/
void setLineWidth(int line_width)
{
this->line_width = line_width;
calculateExtrusion();
}
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)
void setFlow(int flow)
{
speed_current = (speed_base*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
this->flow = flow;
calculateExtrusion();
}
/*!
* Can only be called after the layer height has been set (which is done while writing the gcode!)
*/
void smoothSpeed(int min_speed, int layer_nr, int max_speed_layer)
{
speed = (speed*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
}
double getExtrusionMM3perMM()
{
return extrusion_mm3_per_mm;
}
/*!
* Get the movement speed in mm/s
*/
double getSpeed()
int getSpeed()
{
return speed_current;
return speed;
}
int getLineWidth()
{
return line_width;
}
bool isTravelPath()
{
return line_width == 0;
}
double getFlowPercentage()
{
return flow;
}
private:
void calculateExtrusion()
{
@@ -130,189 +90,108 @@ private:
//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.
class GCodeExport : public NoCopy
class GCodeExport
{
private:
struct ExtruderTrainAttributes
{
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;
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.
double prime_volume; //!< Amount of material (in mm^3) to be primed after an unretration (due to oozing and/or coasting)
double last_retraction_prime_speed; //!< The last prime speed (in mm/s) of the to-be-primed amount
std::deque<double> extruded_volume_at_previous_n_retractions; // in mm^3
ExtruderTrainAttributes()
: 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)
, retraction_e_amount_current(0.0)
, retraction_e_amount_at_e_start(0.0)
, prime_volume(0.0)
, last_retraction_prime_speed(0.0)
{ }
};
ExtruderTrainAttributes extruder_attr[MAX_EXTRUDERS];
bool use_extruder_offset_to_offset_coords;
std::ostream* output_stream;
double current_e_value; //!< The last E value written to gcode (in mm or mm^3)
double extrusion_amount; // in mm or mm^3
double extruderSwitchRetraction;
int extruderSwitchRetractionSpeed;
int extruderSwitchPrimeSpeed;
double retraction_extrusion_window;
int retraction_count_max;
std::deque<double> extrusion_amount_at_previous_n_retractions; // in mm or mm^3
Point3 currentPosition;
double currentSpeed; //!< The current speed (F values / 60) in mm/s
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)
Point3 startPosition;
Point extruderOffset[MAX_EXTRUDERS];
char extruderCharacter[MAX_EXTRUDERS];
int currentTemperature[MAX_EXTRUDERS];
int currentSpeed;
int zPos;
bool isRetracted;
bool isZHopped;
int retractionPrimeSpeed;
int current_extruder;
int currentFanSpeed;
EGCodeFlavor flavor;
double totalPrintTime; //!< The total estimated print time in seconds
std::string preSwitchExtruderCode[MAX_EXTRUDERS];
std::string postSwitchExtruderCode[MAX_EXTRUDERS];
double totalFilament[MAX_EXTRUDERS]; // in mm^3
double filament_diameter[MAX_EXTRUDERS]; // in mm^3
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
public:
GCodeExport();
~GCodeExport();
void setLayerNr(unsigned int layer_nr);
void setOutputStream(std::ostream* stream);
void setExtruderOffset(int id, Point p);
Point getExtruderOffset(int id);
Point getGcodePos(int64_t x, int64_t y, int extruder_train);
void setSwitchExtruderCode(int id, std::string preSwitchExtruderCode, std::string postSwitchExtruderCode);
void setFlavor(EGCodeFlavor flavor);
EGCodeFlavor getFlavor();
void setRetractionSettings(int extruderSwitchRetraction, int extruderSwitchRetractionSpeed, int extruderSwitchPrimeSpeed, int minimalExtrusionBeforeRetraction, int retraction_count_max);
void setZ(int z);
void addLastCoastedVolume(double last_coasted_volume)
{
extruder_attr[current_extruder].prime_volume += last_coasted_volume;
}
Point3 getPosition();
Point getPositionXY();
void resetStartPosition();
Point getStartPositionXY();
int getPositionZ();
int getExtruderNr();
void setFilamentDiameter(unsigned int n, int diameter);
double getFilamentArea(unsigned int extruder);
double getCurrentExtrudedVolume();
double getExtrusionAmountMM3(unsigned int extruder);
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);
void writeLayerComment(int layer_nr);
void writeLayerCountComment(int layer_count);
void writeLine(const char* line);
/*!
* Reset the current_e_value to prevent too high E values.
*
* The current extruded volume is added to the current extruder_attr.
*/
void resetExtrusionValue();
void writeDelay(double timeAmount);
void writeMove(Point p, double speed, double extrusion_per_mm);
void writeMove(Point p, int speed, double extrusion_per_mm);
void writeMove(Point3 p, double speed, double extrusion_per_mm);
void writeMove(Point3 p, int speed, double extrusion_per_mm);
private:
void writeMove(int x, int y, int z, double speed, double extrusion_per_mm);
/*!
* The writeMove when flavor == BFB
*/
void writeMoveBFB(int x, int y, int z, double speed, double extrusion_per_mm);
void writeMove(int x, int y, int z, int speed, double extrusion_per_mm);
public:
void writeRetraction(RetractionConfig* config, bool force=false);
void writeRetraction_extruderSwitch();
void switchExtruder(int newExtruder);
void writeCode(const char* str);
void writeFanCommand(double speed);
void writeFanCommand(int speed);
void writeTemperatureCommand(int extruder, double temperature, bool wait = false);
void writeBedTemperatureCommand(double temperature, bool wait = false);
void preSetup(MeshGroup* settings)
{
for(int n=0; n<settings->getSettingAsCount("machine_extruder_count"); n++)
{
ExtruderTrain* train = settings->getExtruderTrain(n);
setFilamentDiameter(n, train->getSettingInMicrons("material_diameter"));
extruder_attr[n].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
extruder_attr[n].start_code = train->getSettingString("machine_extruder_start_code");
extruder_attr[n].end_code = train->getSettingString("machine_extruder_end_code");
extruder_attr[n].extruder_switch_retraction_distance = INT2MM(train->getSettingInMicrons("switch_extruder_retraction_amount"));
extruder_attr[n].extruderSwitchRetractionSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_retraction_speed");
extruder_attr[n].extruderSwitchPrimeSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_prime_speed");
extruder_attr[n].last_retraction_prime_speed = train->getSettingInMillimetersPerSecond("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured...
}
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);
void writeTemperatureCommand(int extruder, int temperature, bool wait = false);
void writeBedTemperatureCommand(int temperature, bool wait = false);
void finalize(int maxObjectHeight, int moveSpeed, const char* endCode);
};
}
+189 -770
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+66 -455
Ver Arquivo
@@ -7,507 +7,118 @@
#include "comb.h"
#include "utils/polygon.h"
#include "utils/logoutput.h"
#include "wallOverlap.h"
#include "commandSocket.h"
#include "FanSpeedLayerTime.h"
#include "SpaceFillType.h"
namespace cura
{
class SliceDataStorage;
/*!
* A gcode command to insert before a specific path.
*
* Currently only used for preheat commands
*/
struct NozzleTempInsert
{
const unsigned int path_idx; //!< The path before which to insert this command
double time_after_path_start; //!< The time after the start of the path, before which to insert the command // TODO: use this to insert command in between moves in a path!
int extruder; //!< The extruder for which to set the temp
double temperature; //!< The temperature of the temperature command to insert
bool wait; //!< Whether to wait for the temperature to be reached
NozzleTempInsert(unsigned int path_idx, int extruder, double temperature, bool wait, double time_after_path_start = 0.0)
: path_idx(path_idx)
, time_after_path_start(time_after_path_start)
, extruder(extruder)
, temperature(temperature)
, wait(wait)
{}
/*!
* Write the temperature command at the current position in the gcode.
* \param gcode The actual gcode writer
*/
void write(GCodeExport& gcode)
{
gcode.writeTemperatureCommand(extruder, temperature, wait);
}
};
class GCodePlanner; // forward declaration so that TimeMaterialEstimates can be a friend
/*!
* Time and material estimates for a portion of paths, e.g. layer, extruder plan, path.
*/
class TimeMaterialEstimates
{
friend class GCodePlanner;
private:
double extrude_time; //!< in seconds
double unretracted_travel_time; //!< in seconds
double retracted_travel_time; //!< in seconds
double material; //!< in mm^3
public:
TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material)
: extrude_time(extrude_time)
, unretracted_travel_time(unretracted_travel_time)
, retracted_travel_time(retracted_travel_time)
, material(material)
{
}
TimeMaterialEstimates()
: extrude_time(0.0)
, unretracted_travel_time(0.0)
, retracted_travel_time(0.0)
, material(0.0)
{
}
/*!
* Set all estimates to zero.
*/
void reset()
{
extrude_time = 0.0;
unretracted_travel_time = 0.0;
retracted_travel_time = 0.0;
material = 0.0;
}
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);
}
TimeMaterialEstimates& operator+=(const TimeMaterialEstimates& other)
{
extrude_time += other.extrude_time;
unretracted_travel_time += other.unretracted_travel_time;
retracted_travel_time += other.retracted_travel_time;
material += other.material;
return *this;
}
/*!
* \brief Subtracts the specified estimates from these estimates and returns
* the result.
*
* Each of the estimates in this class are individually subtracted.
*
* \param other The estimates to subtract from these estimates.
* \return These estimates with the specified estimates subtracted.
*/
TimeMaterialEstimates operator-(const TimeMaterialEstimates& other);
/*!
* \brief Subtracts the specified elements from these estimates.
*
* This causes the estimates in this instance to change. Each of the
* estimates in this class are individually subtracted.
*
* \param other The estimates to subtract from these estimates.
* \return A reference to this instance.
*/
TimeMaterialEstimates& operator-=(const TimeMaterialEstimates& other);
double getTotalTime() const
{
return extrude_time + unretracted_travel_time + retracted_travel_time;
}
double getTotalUnretractedTime() const
{
return extrude_time + unretracted_travel_time;
}
double getTravelTime() const
{
return retracted_travel_time + unretracted_travel_time;
}
double getExtrudeTime() const
{
return extrude_time;
}
double getMaterial() const
{
return material;
}
};
class GCodePath
{
public:
GCodePathConfig* config; //!< The configuration settings of the path.
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
float flow; //!< A type-independent flow configuration (used for wall overlap compensation)
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
std::vector<Point> points; //!< The points constituting this path.
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.
TimeMaterialEstimates estimates; //!< Naive time and material estimates
bool isTravelPath()
{
return config->isTravelPath();
}
/*!
* Can only be called after the layer height has been set (which is done while writing the gcode!)
*/
double getExtrusionMM3perMM()
{
return flow * config->getExtrusionMM3perMM();
}
/*!
* Get the actual line width (modulated by the flow)
* \return the actual line width as shown in layer view
*/
int getLineWidth()
{
return flow * config->getLineWidth() * config->getFlowPercentage() / 100.0;
}
GCodePathConfig* config;
bool retract;
int extruder;
std::vector<Point> points;
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.
};
class ExtruderPlan
//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.
class GCodePlanner
{
public:
std::vector<GCodePath> paths;
std::list<NozzleTempInsert> inserts;
int extruder; //!< The extruder used for this paths in the current plan.
double required_temp;
TimeMaterialEstimates estimates;
ExtruderPlan(int extruder)
: extruder(extruder)
, required_temp(-1)
{
}
/*!
* Add a new Insert, constructed with the given arguments
*/
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
*/
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
*/
void handleAllRemainingInserts(GCodeExport& gcode)
{
while ( ! inserts.empty() )
{ // handle the Insert to be inserted before this path_idx (and all inserts not handled yet)
NozzleTempInsert& insert = inserts.front();
assert(insert.path_idx == paths.size());
insert.write(gcode);
inserts.pop_front();
}
}
};
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.
*/
class GCodePlanner : public NoCopy
{
friend class LayerPlanBuffer;
private:
SliceDataStorage& storage;
GCodeExport& gcode;
int layer_nr;
int z;
int layer_thickness;
Point start_position;
Point lastPosition;
std::vector<ExtruderPlan> extruder_plans; //!< should always contain at least one ExtruderPlan
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.
std::vector<GCodePath> paths;
Comb* comb;
RetractionConfig* last_retraction_config;
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings;
double extrudeSpeedFactor;
double travelSpeedFactor;
double fan_speed;
GCodePathConfig travelConfig;
int extrudeSpeedFactor;
int travelSpeedFactor;
int currentExtruder;
int retractionMinimalDistance;
bool forceRetraction;
bool alwaysRetract;
double extraTime;
double totalPrintTime;
private:
/*!
* Either create a new path with the given config or return the last path if it already had that config.
* If GCodePlanner::forceNewPathStart has been called a new path will always be returned.
*
* \param config The config used for the path returned
* \param space_fill_type The type of space filling which this path employs
* \param flow (optional) A ratio for the extrusion speed
* \return A path with the given config which is now the last path in GCodePlanner::paths
*/
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
/*!
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
*
* This function is introduced because in some cases
* GCodePlanner::getLatestPathWithConfig is called consecutively with the same config pointer,
* though the content of the config has changed.
*
* Example cases:
* - when changing extruder, the same travel config is used, but its extruder field is changed.
*/
GCodePath* getLatestPathWithConfig(GCodePathConfig* config);
void forceNewPathStart();
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
*/
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(GCodeExport& gcode, RetractionConfig* retraction_config, int travelSpeed, int retractionMinimalDistance);
~GCodePlanner();
private:
/*!
* Compute the boundary within which to comb, or to move into when performing a retraction.
* \return the comb_boundary_inside
*/
Polygons computeCombBoundaryInside();
public:
int getLayerNr()
bool setExtruder(int extruder)
{
return layer_nr;
}
Point getLastPosition()
{
return lastPosition;
if (extruder == currentExtruder)
return false;
currentExtruder = extruder;
return true;
}
/*!
* send a polygon through the command socket from the previous point to the given point
*/
void sendPolygon(PrintFeatureType print_feature_type, Point from, Point to, int line_width)
{
if (CommandSocket::isInstantiated())
{
// we should send this travel as a non-retraction move
cura::Polygons pathPoly;
PolygonRef path = pathPoly.newPoly();
path.add(from);
path.add(to);
CommandSocket::getInstance()->sendPolygons(print_feature_type, layer_nr, pathPoly, line_width);
}
}
/*!
* Set whether the next destination is inside a layer part or not.
*
* Features like infill, walls, skin etc. are considered inside.
* Features like prime tower and support are considered outside.
*/
void setIsInside(bool going_to_comb);
bool setExtruder(int extruder);
/*!
* Get the last planned extruder.
*/
int getExtruder()
{
return extruder_plans.back().extruder;
return currentExtruder;
}
void setExtrudeSpeedFactor(double speedFactor)
void setCombBoundary(Polygons* polygons)
{
if (comb)
delete comb;
if (polygons)
comb = new Comb(*polygons);
else
comb = nullptr;
}
void setAlwaysRetract(bool alwaysRetract)
{
this->alwaysRetract = alwaysRetract;
}
void forceRetract()
{
forceRetraction = true;
}
void setExtrudeSpeedFactor(int speedFactor)
{
if (speedFactor < 1) speedFactor = 1;
this->extrudeSpeedFactor = speedFactor;
}
double getExtrudeSpeedFactor()
int getExtrudeSpeedFactor()
{
return this->extrudeSpeedFactor;
}
void setTravelSpeedFactor(double speedFactor)
void setTravelSpeedFactor(int speedFactor)
{
if (speedFactor < 1) speedFactor = 1.0;
if (speedFactor < 1) speedFactor = 1;
this->travelSpeedFactor = speedFactor;
}
double getTravelSpeedFactor()
int getTravelSpeedFactor()
{
return this->travelSpeedFactor;
}
void setFanSpeed(double _fan_speed)
{
fan_speed = _fan_speed;
}
/*!
* Add a travel path to a certain point, retract if needed and when avoiding boundary crossings:
* avoiding obstacles and comb along the boundary of parts.
*
* \param p The point to travel to
*/
void addTravel(Point p);
/*!
* Add a travel path to a certain point and retract if needed.
*
* No combing is performed.
*
* \param p The point to travel to
* \param path (optional) The travel path to which to add the point \p p
*/
void addTravel_simple(Point p, GCodePath* path = nullptr);
/*!
* Add an extrusion move to a certain point, optionally with a different flow than the one in the \p config.
*
* \param p The point to extrude to
* \param config The config with which to extrude
* \param space_fill_type Of what space filling type this extrusion move is a part
* \param flow A modifier of the extrusion width which would follow from the \p config
*/
void addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
void addExtrusionMove(Point p, GCodePathConfig* config);
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr);
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.
* \param polygons The lines
* \param config The config of the lines
* \param space_fill_type The type of space filling used to generate the line segments (should be either Lines or PolyLines!)
* \param wipe_dist (optional) the distance wiped without extruding after laying down a line.
*/
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist = 0);
/*!
* Compute naive time estimates (without accountign for slow down at corners etc.) and naive material estimates (without accounting for MergeInfillLines)
* and store them in each ExtruderPlan and each GCodePath.
*
* \return the total estimates of this layer
*/
TimeMaterialEstimates computeNaiveTimeEstimates();
void forceMinimalLayerTime(double minTime, double minimalSpeed, double travelTime, double extrusionTime);
/*!
* Write the planned paths to gcode
*
* \param gcode The gcode to write the planned paths to
*/
void writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int layerThickness);
/*!
* Complete all GcodePathConfig s by
* - altering speed to conform to speed_layer_0
* - setting the layer_height (and thereby computing the extrusionMM3perMM)
*/
void completeConfigs();
/*!
* Interpolate between the initial layer speeds and the eventual speeds.
*/
void processInitialLayersSpeedup();
/*!
* Whether the current retracted path is to be an extruder switch retraction.
* This function is used to avoid a G10 S1 after a G10.
*
* \param gcode The gcode to write the planned paths to
* \param extruder_plan_idx The index of the current extruder plan
* \param path_idx The index of the current retracted path
* \return Whether the path should be an extgruder switch retracted path
*/
bool makeRetractSwitchRetract(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx);
/*!
* Writes a path to GCode and performs coasting, or returns false if it did nothing.
*
* Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines.
*
* \param gcode The gcode to write the planned paths to
* \param extruder_plan_idx The index of the current extruder plan
* \param path_idx The index into GCodePlanner::paths for the next path to be written to GCode.
* \param layerThickness The height of the current layer.
* \param coasting_volume The volume otherwise leaked during a normal move.
* \param coasting_speed The speed at which to move during move-coasting.
* \param coasting_min_volume The minimal volume a path should have (before starting to coast) which builds up enough pressure to ooze as much as \p coasting_volume.
* \return Whether any GCode has been written for the path.
*/
bool writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume, 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.
*/
void processFanSpeedAndMinimalLayerTime();
/*!
* Add a travel move to the layer plan to move inside the current layer part by a given distance away from the outline.
* This is supposed to be called when the nozzle is around the boundary of a layer part, not when the nozzle is in the middle of support, or in the middle of the air.
*
* \param distance The distance to the comb boundary after we moved inside it.
*/
void moveInsideCombBoundary(int distance);
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config);
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config);
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config);
void forceMinimalLayerTime(double minTime, int minimalSpeed, double travelTime, double extrusionTime);
void getTimes(double& travelTime, double& extrudeTime);
void writeGCode(bool liftHeadIfNeeded, int layerThickness);
};
}//namespace cura
+374 -189
Ver Arquivo
@@ -2,51 +2,11 @@
#include "infill.h"
#include "functional"
#include "utils/polygonUtils.h"
#include "utils/logoutput.h"
namespace cura {
void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between)
{
if (in_outline.size() == 0) return;
if (line_distance == 0) return;
const Polygons* outline = &in_outline;
Polygons outline_offsetted;
switch(pattern)
{
case EFillMethod::GRID:
generateGridInfill(result_lines);
break;
case EFillMethod::LINES:
generateLineInfill(result_lines, line_distance, fill_angle);
break;
case EFillMethod::TRIANGLES:
generateTriangleInfill(result_lines);
break;
case EFillMethod::CONCENTRIC:
PolygonUtils::offsetSafe(in_outline, outline_offset - infill_line_width / 2, infill_line_width, outline_offsetted, false); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
outline = &outline_offsetted;
if (abs(infill_line_width - line_distance) < 10)
{
generateConcentricInfillDense(*outline, result_polygons, in_between, remove_overlapping_perimeters);
}
else
{
generateConcentricInfill(*outline, result_polygons, line_distance);
}
break;
case EFillMethod::ZIG_ZAG:
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
break;
default:
logError("Fill pattern has unknown value.\n");
break;
}
}
void Infill::generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, bool avoidOverlappingPerimeters)
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters)
{
while(outline.size() > 0)
{
@@ -56,13 +16,13 @@ void Infill::generateConcentricInfillDense(Polygons outline, Polygons& result, P
result.add(r);
}
Polygons next_outline;
PolygonUtils::offsetExtrusionWidth(outline, true, infill_line_width, next_outline, in_between, avoidOverlappingPerimeters);
offsetExtrusionWidth(outline, true, extrusionWidth, next_outline, in_between, avoidOverlappingPerimeters);
outline = next_outline;
}
}
}
void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
{
while(outline.size() > 0)
{
@@ -76,95 +36,151 @@ void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int in
}
void Infill::generateGridInfill(Polygons& result)
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result,
int extrusionWidth, int lineSpacing, int infillOverlap,
double rotation)
{
generateLineInfill(result, line_distance * 2, fill_angle);
generateLineInfill(result, line_distance * 2, fill_angle + 90);
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
infillOverlap, rotation);
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
infillOverlap, rotation + 90);
}
void Infill::generateTriangleInfill(Polygons& result)
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result,
int extrusionWidth, int lineSpacing, int infillOverlap,
double rotation)
{
generateLineInfill(result, line_distance * 3, fill_angle);
generateLineInfill(result, line_distance * 3, fill_angle + 60);
generateLineInfill(result, line_distance * 3, fill_angle + 120);
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
infillOverlap, rotation);
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
infillOverlap, rotation + 60);
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
infillOverlap, rotation + 120);
}
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)
void addLineInfill(Polygons& result, PointMatrix matrix, int scanline_min_idx, int lineSpacing, AABB boundary, std::vector<std::vector<int64_t> > cutList, int extrusionWidth)
{
auto addLine = [&](Point from, Point to)
{
{
PolygonRef p = result.newPoly();
p.add(rotation_matrix.unapply(from));
p.add(rotation_matrix.unapply(to));
p.add(matrix.unapply(from));
p.add(matrix.unapply(to));
};
auto compare_int64_t = [](const void* a, const void* b)
{
int64_t n = (*(int64_t*)a) - (*(int64_t*)b);
if (n < 0)
{
return -1;
}
if (n > 0)
{
return 1;
}
if (n < 0) return -1;
if (n > 0) return 1;
return 0;
};
int scanline_idx = 0;
for(int64_t x = scanline_min_idx * line_distance; x < boundary.max.X; x += line_distance)
for(int64_t x = scanline_min_idx * lineSpacing; x < boundary.max.X; x += lineSpacing)
{
std::vector<int64_t>& crossings = cut_list[scanline_idx];
qsort(crossings.data(), crossings.size(), sizeof(int64_t), compare_int64_t);
for(unsigned int crossing_idx = 0; crossing_idx + 1 < crossings.size(); crossing_idx += 2)
qsort(cutList[scanline_idx].data(), cutList[scanline_idx].size(), sizeof(int64_t), compare_int64_t);
for(unsigned int i = 0; i + 1 < cutList[scanline_idx].size(); i+=2)
{
if (crossings[crossing_idx + 1] - crossings[crossing_idx] < infill_line_width / 5)
{ // segment is too short to create infill
if (cutList[scanline_idx][i+1] - cutList[scanline_idx][i] < extrusionWidth / 5)
continue;
}
addLine(Point(x, crossings[crossing_idx]), Point(x, crossings[crossing_idx + 1]));
addLine(Point(x, cutList[scanline_idx][i]), Point(x, cutList[scanline_idx][i+1]));
}
scanline_idx += 1;
}
}
void Infill::generateLineInfill(Polygons& result, int line_distance, const double& fill_angle)
/*!
* generate lines within the area of \p in_outline, at regular intervals of \p lineSpacing
*
* idea:
* intersect a regular grid of 'scanlines' with the area inside \p in_outline
*
* we call the areas between two consecutive scanlines a 'scansegment'.
* Scansegment x is the area between scanline x and scanline x+1
*
* algorithm:
* 1) for each line segment of each polygon:
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
* (zigzag): add boundary segments to result
* 2) for each scanline:
* sort the associated intersections
* and connect them using the even-odd rule
*
*/
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation)
{
PointMatrix rotation_matrix(fill_angle);
NoZigZagConnectorProcessor lines_processor(rotation_matrix, result);
bool connected_zigzags = false;
bool safe_outline_offset = false;
generateLinearBasedInfill(outline_offset, safe_outline_offset, result, line_distance, rotation_matrix, lines_processor, connected_zigzags);
if (in_outline.size() == 0) return;
Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 + outlineOffset);
if (outline.size() == 0) return;
PointMatrix matrix(rotation);
outline.applyMatrix(matrix);
AABB boundary(outline);
int scanline_min_idx = boundary.min.X / lineSpacing;
int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx;
std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments
for(int n=0; n<lineCount; n++)
cutList.push_back(std::vector<int64_t>());
for(unsigned int poly_idx=0; poly_idx < outline.size(); poly_idx++)
{
Point p0 = outline[poly_idx][outline[poly_idx].size()-1];
for(unsigned int i=0; i < outline[poly_idx].size(); i++)
{
Point p1 = outline[poly_idx][i];
int64_t xMin = p1.X, xMax = p0.X;
if (xMin == xMax) {
p0 = p1;
continue;
}
if (xMin > xMax) { xMin = p0.X; xMax = p1.X; }
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
int direction = 1;
if (p0.X > p1.X)
{
direction = -1;
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
} else scanline_idx0 += 1; // only consider the scanlines in between the scansegments
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction)
{
int x = scanline_idx * lineSpacing;
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
cutList[scanline_idx - scanline_min_idx].push_back(y);
}
p0 = p1;
}
}
addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth);
}
void Infill::generateZigZagInfill(Polygons& result, const int line_distance, const double& fill_angle, const bool connected_zigzags, const bool use_endpieces)
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces)
{
bool safe_outline_offset = true;
PointMatrix rotation_matrix(fill_angle);
if (use_endpieces)
{
if (connected_zigzags)
{
ZigzagConnectorProcessorConnectedEndPieces zigzag_processor(rotation_matrix, result);
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
}
else
{
ZigzagConnectorProcessorDisconnectedEndPieces zigzag_processor(rotation_matrix, result);
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
}
}
else
{
ZigzagConnectorProcessorNoEndPieces zigzag_processor(rotation_matrix, result);
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
}
if (use_endPieces) return generateZigZagInfill_endPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation, connect_zigzags);
else return generateZigZagInfill_noEndPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation);
}
/*
/*!
* adapted from generateLineInfill(.)
*
* generate lines within the area of [in_outline], at regular intervals of [lineSpacing]
* idea:
* intersect a regular grid of 'scanlines' with the area inside [in_outline]
* sigzag:
* include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_|
*
* we call the areas between two consecutive scanlines a 'scansegment'
*
* algorithm:
* 1. for each line segment of each polygon:
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
@@ -173,119 +189,288 @@ void Infill::generateZigZagInfill(Polygons& result, const int line_distance, con
* sort the associated intersections
* and connect them using the even-odd rule
*
* rough explanation of the zigzag algorithm:
* zigzag algorithm:
* while walking around (each) polygon (1.)
* if polygon intersects with even scanline
* start boundary segment (add each following segment to the [result])
* when polygon intersects with a scanline again
* stop boundary segment (stop adding segments to the [result])
* (see infill/ZigzagConnectorProcessor.h for actual implementation details)
* if polygon intersects with even scanline again (instead of odd)
* dont add the last line segment to the boundary (unless [connect_zigzags])
*
*
* we call the areas between two consecutive scanlines a 'scansegment'.
* Scansegment x is the area between scanline x and scanline x+1
* Edit: the term scansegment is wrong, since I call a boundary segment leaving from an even scanline to the left as belonging to an even scansegment,
* while I also call a boundary segment leaving from an even scanline toward the right as belonging to an even scansegment.
* <--
* ___
* | | |
* | | |
* | |___|
* -->
*
* ^ = even scanline
*
* start boundary from even scanline! :D
*
*
* _____
* | | | ,
* | | | |
* |_____| |__/
*
* ^ ^ ^ scanlines
* ^ disconnected end piece
*/
void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags)
void generateZigZagInfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags)
{
if (line_distance == 0)
{
return;
}
if (in_outline.size() == 0)
{
return;
}
// if (in_outline.size() == 0) return;
// Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2);
Polygons empty;
Polygons outline = in_outline.difference(empty); // copy
if (outline.size() == 0) return;
Polygons outline;
if (outline_offset != 0)
{
PolygonUtils::offsetSafe(in_outline, outline_offset, infill_line_width, outline, remove_overlapping_perimeters && safe_outline_offset);
}
else
{
outline = in_outline;
}
PointMatrix matrix(rotation);
outline = outline.offset(infill_overlap);
outline.applyMatrix(matrix);
if (outline.size() == 0)
{
return;
}
outline.applyMatrix(rotation_matrix);
auto addLine = [&](Point from, Point to)
{
PolygonRef p = result.newPoly();
p.add(matrix.unapply(from));
p.add(matrix.unapply(to));
};
AABB boundary(outline);
int scanline_min_idx = boundary.min.X / line_distance;
int line_count = (boundary.max.X + (line_distance - 1)) / line_distance - scanline_min_idx;
std::vector<std::vector<int64_t> > cut_list; // mapping from scanline to all intersections with polygon segments
for(int scanline_idx = 0; scanline_idx < line_count; scanline_idx++)
int scanline_min_idx = boundary.min.X / lineSpacing;
int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx;
std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments
for(int n=0; n<lineCount; n++)
cutList.push_back(std::vector<int64_t>());
for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++)
{
cut_list.push_back(std::vector<int64_t>());
}
for(unsigned int poly_idx = 0; poly_idx < outline.size(); poly_idx++)
{
PolygonRef poly = outline[poly_idx];
Point p0 = poly.back();
zigzag_connector_processor.registerVertex(p0); // always adds the first point to ZigzagConnectorProcessorEndPieces::first_zigzag_connector when using a zigzag infill type
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
std::vector<Point> firstBoundarySegment;
std::vector<Point> unevenBoundarySegment; // stored cause for connected_zigzags a boundary segment which ends in an uneven scanline needs to be included
bool isFirstBoundarySegment = true;
bool firstBoundarySegmentEndsInEven;
bool isEvenScanSegment = false;
Point p0 = outline[polyNr][outline[polyNr].size()-1];
Point lastPoint = p0;
for(unsigned int i=0; i < outline[polyNr].size(); i++)
{
Point p1 = poly[point_idx];
if (p1.X == p0.X)
{
zigzag_connector_processor.registerVertex(p1);
// TODO: how to make sure it always adds the shortest line? (in order to prevent overlap with the zigzag connectors)
// note: this is already a problem for normal infill, but hasn't really cothered anyone so far.
Point p1 = outline[polyNr][i];
int64_t xMin = p1.X, xMax = p0.X;
if (xMin == xMax) {
lastPoint = p1;
p0 = p1;
continue;
}
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -line_distance)) / line_distance; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -line_distance)) / line_distance; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
// this way of handling the indices takes care of the case where a boundary line segment ends exactly on a scanline:
// in case the next segment moves back from that scanline either 2 or 0 scanline-boundary intersections are created
// otherwise only 1 will be created, counting as an actual intersection
if (xMin > xMax) { xMin = p0.X; xMax = p1.X; }
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
int direction = 1;
if (p0.X > p1.X)
{
direction = -1;
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
}
else
} else scanline_idx0 += 1; // only consider the scanlines in between the scansegments
if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0);
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction)
{
scanline_idx0 += 1; // only consider the scanlines in between the scansegments
}
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1 + direction; scanline_idx += direction)
{
int x = scanline_idx * line_distance;
int x = scanline_idx * lineSpacing;
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
cut_list[scanline_idx - scanline_min_idx].push_back(y);
Point scanline_linesegment_intersection(x, y);
zigzag_connector_processor.registerScanlineSegmentIntersection(scanline_linesegment_intersection, scanline_idx % 2 == 0);
cutList[scanline_idx - scanline_min_idx].push_back(y);
bool last_isEvenScanSegment = isEvenScanSegment;
if (scanline_idx % 2 == 0) isEvenScanSegment = true;
else isEvenScanSegment = false;
if (!isFirstBoundarySegment)
{
if (last_isEvenScanSegment && (connect_zigzags || !isEvenScanSegment))
addLine(lastPoint, Point(x,y));
else if (connect_zigzags && !last_isEvenScanSegment && !isEvenScanSegment) // if we end an uneven boundary in an uneven segment
{ // add whole unevenBoundarySegment (including the just obtained point)
for (unsigned int p = 1; p < unevenBoundarySegment.size(); p++)
{
addLine(unevenBoundarySegment[p-1], unevenBoundarySegment[p]);
}
addLine(unevenBoundarySegment[unevenBoundarySegment.size()-1], Point(x,y));
unevenBoundarySegment.clear();
}
if (connect_zigzags && last_isEvenScanSegment && !isEvenScanSegment)
unevenBoundarySegment.push_back(Point(x,y));
else
unevenBoundarySegment.clear();
}
lastPoint = Point(x,y);
if (isFirstBoundarySegment)
{
firstBoundarySegment.emplace_back(x,y);
firstBoundarySegmentEndsInEven = isEvenScanSegment;
isFirstBoundarySegment = false;
}
}
zigzag_connector_processor.registerVertex(p1);
if (!isFirstBoundarySegment)
{
if (isEvenScanSegment)
addLine(lastPoint, p1);
else if (connect_zigzags)
unevenBoundarySegment.push_back(p1);
}
lastPoint = p1;
p0 = p1;
}
zigzag_connector_processor.registerPolyFinished();
}
if (cut_list.size() == 0)
{
return;
}
if (connected_zigzags && cut_list.size() == 1 && cut_list[0].size() <= 2)
{
return; // don't add connection if boundary already contains whole outline!
}
addLineInfill(result, rotation_matrix, scanline_min_idx, line_distance, boundary, cut_list);
if (isEvenScanSegment || isFirstBoundarySegment || connect_zigzags)
{
for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++)
{
if (i < firstBoundarySegment.size() - 1 || !firstBoundarySegmentEndsInEven || connect_zigzags) // only add last element if connect_zigzags or boundary segment ends in uneven scanline
addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]);
}
}
else if (!firstBoundarySegmentEndsInEven)
addLine(firstBoundarySegment[firstBoundarySegment.size()-2], firstBoundarySegment[firstBoundarySegment.size()-1]);
}
if (cutList.size() == 0) return;
if (connect_zigzags && cutList.size() == 1 && cutList[0].size() <= 2) return; // don't add connection if boundary already contains whole outline!
addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth);
}
void generateZigZagInfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation)
{
if (in_outline.size() == 0) return;
Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2);
if (outline.size() == 0) return;
PointMatrix matrix(rotation);
outline.applyMatrix(matrix);
auto addLine = [&](Point from, Point to)
{
PolygonRef p = result.newPoly();
p.add(matrix.unapply(from));
p.add(matrix.unapply(to));
};
AABB boundary(outline);
int scanline_min_idx = boundary.min.X / lineSpacing;
int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx;
std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments
for(int n=0; n<lineCount; n++)
cutList.push_back(std::vector<int64_t>());
for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++)
{
std::vector<Point> firstBoundarySegment;
std::vector<Point> boundarySegment;
bool isFirstBoundarySegment = true;
bool firstBoundarySegmentEndsInEven;
bool isEvenScanSegment = false;
Point p0 = outline[polyNr][outline[polyNr].size()-1];
for(unsigned int i=0; i < outline[polyNr].size(); i++)
{
Point p1 = outline[polyNr][i];
int64_t xMin = p1.X, xMax = p0.X;
if (xMin == xMax) {
p0 = p1;
continue;
}
if (xMin > xMax) { xMin = p0.X; xMax = p1.X; }
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
int direction = 1;
if (p0.X > p1.X)
{
direction = -1;
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
} else scanline_idx0 += 1; // only consider the scanlines in between the scansegments
if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0);
else boundarySegment.push_back(p0);
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction)
{
int x = scanline_idx * lineSpacing;
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
cutList[scanline_idx - scanline_min_idx].push_back(y);
bool last_isEvenScanSegment = isEvenScanSegment;
if (scanline_idx % 2 == 0) isEvenScanSegment = true;
else isEvenScanSegment = false;
if (!isFirstBoundarySegment)
{
if (last_isEvenScanSegment && !isEvenScanSegment)
{ // add whole boundarySegment (including the just obtained point)
for (unsigned int p = 1; p < boundarySegment.size(); p++)
{
addLine(boundarySegment[p-1], boundarySegment[p]);
}
addLine(boundarySegment[boundarySegment.size()-1], Point(x,y));
boundarySegment.clear();
}
else if (isEvenScanSegment) // we are either in an end piece or an uneven boundary segment
{
boundarySegment.clear();
boundarySegment.emplace_back(x,y);
} else
boundarySegment.clear();
}
if (isFirstBoundarySegment)
{
firstBoundarySegment.emplace_back(x,y);
firstBoundarySegmentEndsInEven = isEvenScanSegment;
isFirstBoundarySegment = false;
boundarySegment.emplace_back(x,y);
}
}
if (!isFirstBoundarySegment && isEvenScanSegment)
boundarySegment.push_back(p1);
p0 = p1;
}
if (!isFirstBoundarySegment && isEvenScanSegment && !firstBoundarySegmentEndsInEven)
{
for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++)
addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]);
}
}
addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth);
}
}//namespace cura
+9 -174
Ver Arquivo
@@ -3,182 +3,17 @@
#define INFILL_H
#include "utils/polygon.h"
#include "settings.h"
// #include "ZigzagConnectorProcessor.h"
#include "infill/ZigzagConnectorProcessor.h"
#include "infill/NoZigZagConnectorProcessor.h"
#include "infill/ActualZigzagConnectorProcessor.h"
#include "infill/ZigzagConnectorProcessorNoEndPieces.h"
#include "infill/ZigzagConnectorProcessorEndPieces.h"
#include "infill/ZigzagConnectorProcessorConnectedEndPieces.h"
#include "infill/ZigzagConnectorProcessorDisconnectedEndPieces.h"
#include "utils/intpoint.h"
#include "utils/AABB.h"
namespace cura
{
class Infill
{
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 fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid)
bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
public:
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, bool remove_overlapping_perimeters, int infill_line_width, int line_distance, int infill_overlap, double fill_angle, bool connected_zigzags = false, bool use_endpieces = false)
: pattern(pattern)
, in_outline(in_outline)
, outline_offset(outline_offset)
, remove_overlapping_perimeters(remove_overlapping_perimeters)
, infill_line_width(infill_line_width)
, line_distance(line_distance)
, infill_overlap(infill_overlap)
, fill_angle(fill_angle)
, connected_zigzags(connected_zigzags)
, use_endpieces(use_endpieces)
{
}
/*!
* Generate the infill.
*
* \param result_polygons (output) The resulting polygons (from concentric infill)
* \param result_lines (output) The resulting line segments (from linear infill types)
* \param in_between (optional output) The areas in between two concecutive concentric infill polygons
*/
void generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between);
private:
/*!
* Generate sparse concentric infill
* \param outline The actual outline of the area within which to generate infill
* \param result (output) The resulting polygons
* \param inset_value The offset between each consecutive two polygons
*/
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
/*!
* Generate dense concentric infill (100%)
*
* \param outline The actual outline of the area within which to generate infill
* \param result (output) The resulting polygons
* \param in_between (output) The areas in between each two consecutive polygons
* \param remove_overlapping_perimeters Whether to remove overlapping perimeter parts
*/
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, bool remove_overlapping_perimeters);
/*!
* Generate a rectangular grid of infill lines
* \param result (output) The resulting lines
*/
void generateGridInfill(Polygons& result);
/*!
* Generate a triangular grid of infill lines
* \param result (output) The resulting lines
*/
void generateTriangleInfill(Polygons& result);
/*!
* Convert a mapping from scanline to line_segment-scanline-intersections (\p cut_list) into line segments, using the even-odd rule
* \param result (output) The resulting lines
* \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill
* \param scanline_min_idx The lowest index of all scanlines crossing the polygon
* \param line_distance The distance between two lines which are in the same direction
* \param boundary The axis aligned boundary box within which the polygon is
* \param cut_list A mapping of each scanline to all y-coordinates (in the space transformed by rotation_matrix) where the polygons are crossing the scanline
*/
void addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list);
/*!
* generate lines within the area of \p in_outline, at regular intervals of \p line_distance
*
* idea:
* intersect a regular grid of 'scanlines' with the area inside \p in_outline
*
* \param result (output) The resulting lines
* \param line_distance The distance between two lines which are in the same direction
* \param fill_angle The angle of the generated lines
*/
void generateLineInfill(Polygons& result, int line_distance, const double& fill_angle);
/*!
* Function for creating linear based infill types (Lines, ZigZag).
*
* This function implements the basic functionality of Infill::generateLineInfill (see doc of that function),
* but makes calls to a ZigzagConnectorProcessor which handles what to do with each line segment - scanline intersection.
*
* It is called only from Infill::generateLineinfill and Infill::generateZigZagInfill.
*
* \param outline_offset An offset from the reference polygon (Infill::in_outline) to get the actual outline within which to generate infill
* \param safe_outline_offset Whether to consider removing overlapping wall parts (not so for normal line infill)
* \param result (output) The resulting lines
* \param line_distance The distance between two lines which are in the same direction
* \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill
* \param zigzag_connector_processor The processor used to generate zigzag connectors
* \param connected_zigzags Whether to connect the endpiece zigzag segments on both sides to the same infill line
*/
void generateLinearBasedInfill(const int outline_offset, bool safe_outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags);
/*!
*
* generate lines within the area of [in_outline], at regular intervals of [line_distance]
* idea:
* intersect a regular grid of 'scanlines' with the area inside [in_outline] (see generateLineInfill)
* zigzag:
* include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_|
*
* Note that ZigZag consists of 3 types:
* - without endpieces
* - with disconnected endpieces
* - with connected endpieces
*
* <--
* ___
* | | |
* | | |
* | |___|
* -->
*
* ^ = even scanline
* ^ ^ no endpieces
*
* start boundary from even scanline! :D
*
*
* v disconnected end piece: leave out last line segment
* _____
* | | | \ .
* | | | |
* |_____| |__/
*
* ^ ^ ^ scanlines
*
*
* v connected end piece
* ________
* | | | \ .
* | | | |
* |_____| |__/ .
*
* ^ ^ ^ scanlines
*
* \param result (output) The resulting lines
* \param line_distance The distance between two lines which are in the same direction
* \param fill_angle The angle of the generated lines
* \param connected_zigzags Whether to connect the endpiece zigzag segments on both sides to the same infill line
* \param use_endpieces Whether to include zigzag segments connecting a scanline to itself
*/
void generateZigZagInfill(Polygons& result, const int line_distance, const double& fill_angle, const bool connected_zigzags, const bool use_endpieces);
};
namespace cura {
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters);
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces);
void generateZigZagInfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags);
void generateZigZagInfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
}//namespace cura
#endif//INFILL_H
-47
Ver Arquivo
@@ -1,47 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_ACTUAL_ZIGZAG_CONNECTOR_PROCESSOR_H
#define INFILL_ACTUAL_ZIGZAG_CONNECTOR_PROCESSOR_H
#include "../utils/polygon.h"
#include "ZigzagConnectorProcessor.h"
#include "../utils/intpoint.h"
namespace cura
{
/*!
* In contrast to NoZigZagConnectorProcessor
*/
class ActualZigzagConnectorProcessor : public ZigzagConnectorProcessor
{
protected:
/*!
* The line segments belonging the zigzag connector to which the very first vertex belongs.
* This will be combined with the last handled zigzag_connector, which combine to a whole zigzag connector.
*
* Because the boundary polygon may start in in the middle of a zigzag connector,
*/
std::vector<Point> first_zigzag_connector;
/*!
* The currently built up zigzag connector (not the first/last) or end piece or discarded boundary segment
*/
std::vector<Point> zigzag_connector;
bool is_first_zigzag_connector; //!< Whether we're still in the first zigzag connector
bool first_zigzag_connector_ends_in_even_scanline; //!< Whether the first zigzag connector ends in an even scanline
bool last_scanline_is_even; //!< Whether the last seen scanline-boundary intersection was with an even scanline
ActualZigzagConnectorProcessor(const PointMatrix& rotation_matrix, Polygons& result)
: ZigzagConnectorProcessor(rotation_matrix, result)
, is_first_zigzag_connector(true)
, first_zigzag_connector_ends_in_even_scanline(true)
, last_scanline_is_even(false)
{
}
};
} // namespace cura
#endif // INFILL_ACTUAL_ZIGZAG_CONNECTOR_PROCESSOR_H
-25
Ver Arquivo
@@ -1,25 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "NoZigZagConnectorProcessor.h"
namespace cura
{
void NoZigZagConnectorProcessor::registerVertex(const Point& vertex)
{
}
void NoZigZagConnectorProcessor::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
{
}
void NoZigZagConnectorProcessor::registerPolyFinished()
{
}
} // namespace cura
-28
Ver Arquivo
@@ -1,28 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_NO_ZIGZAG_CONNECTOR_PROCESSOR_H
#define INFILL_NO_ZIGZAG_CONNECTOR_PROCESSOR_H
#include "../utils/polygon.h"
#include "ZigzagConnectorProcessor.h"
namespace cura
{
class NoZigZagConnectorProcessor : public ZigzagConnectorProcessor
{
public:
NoZigZagConnectorProcessor(const PointMatrix& rotation_matrix, Polygons& result)
: ZigzagConnectorProcessor(rotation_matrix, result)
{
}
void registerVertex(const Point& vertex);
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
void registerPolyFinished();
};
} // namespace cura
#endif // INFILL_NO_ZIGZAG_CONNECTOR_PROCESSOR_H
-154
Ver Arquivo
@@ -1,154 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_H
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_H
#include "../utils/polygon.h"
namespace cura
{
/*!
* Processor class for processing the connections between lines which makes the infill a zigzag pattern.
*
* During the creation of the infill lines, calls are made to a ZigzagConnectorProcessor so that the zigzag connector segments are created
* at the same time as the lines are created.
*
* generate lines within the area of [in_outline], at regular intervals of [line_distance]
* idea:
* intersect a regular grid of 'scanlines' with the area inside [in_outline] (see generateLineInfill)
* zigzag:
* include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_|
*
* we call the areas between two consecutive scanlines a 'scansegment'
*
* algorithm:
* 1. for each line segment of each polygon:
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
* (zigzag): add boundary segments to result
* 2. for each scanline:
* sort the associated intersections
* and connect them using the even-odd rule
*
* zigzag algorithm:
* while walking around (each) polygon (1.)
* if polygon intersects with even scanline
* start boundary segment (add each following segment to the [result])
* when polygon intersects with a scanline again
* stop boundary segment (stop adding segments to the [result])
* if polygon intersects with even scanline again (instead of odd)
* dont add the last line segment to the boundary (unless [connected_zigzags])
*
* Note that ZigZag consists of 3 types:
* - without endpieces
* - with disconnected endpieces
* - with connected endpieces
*
* Each of these has a base class for which ZigzagConnectorProcessor is an ancestor.
* The inheritance structure is as such:
* ZigzagConnectorProcessor
* / \ .
* / \ .
* ActualZigzagConnectorProcessor NoZigZagConnectorProcessor
* / \ for lines infill .
* / \ .
* ZigzagConnectorProcessorEndPieces ZigzagConnectorProcessorNoEndPieces
* / \ for zigzag infill (without end pieces) .
* / \ .
* ZigzagConnectorProcessorConnectedEndPieces ZigzagConnectorProcessorDisconnectedEndPieces
* for zigzag support with normal endpieces for zigzag support with disconnected endpieces for more easy removability
*
* v v zigzag connectors
* <--
* :___: : < scanlines
* | | |
* | | | < infill lines along scanlines
* | |___|
* : : :
* --> winding order of polygon
*
* ^ = even scanline
* ^ ^ no endpieces
*
* start boundary from even scanline! :D
* include only a boundary segment if it starts in an even scanline and ends in an odd scanline
*
* ________
* | | | \ .
* | | | |
* |_____| |__/ .
*
* ^ ^ ^ scanlines
* ^ connected end piece
* include a boundary segment also if it starts in an odd scanline and ends odd,
* or starts in an even scanline and ends in an even scanline,
* but not when it starts in an odd and ends in an even scanline (see top left or bottom middle).
*
* _____
* | | | \ .
* | | | |
* |_____| |__/
*
* ^ ^ ^ scanlines
* ^ disconnected end piece
* Leave out the last line segment of the boundary polygon: from a vertex to the linesegment-scanline intersection.
*/
class ZigzagConnectorProcessor
{
protected:
const PointMatrix& rotation_matrix; //!< The rotation matrix used to enforce the infill angle
Polygons& result; //!< The result of the computation
virtual ~ZigzagConnectorProcessor()
{}
/*!
* Add a line to the result bu unapplying the rotation rotation_matrix.
*
* \param from The one end of the line segment
* \param to The other end of the line segment
*/
void addLine(Point from, Point to)
{
PolygonRef line_poly = result.newPoly();
line_poly.add(rotation_matrix.unapply(from));
line_poly.add(rotation_matrix.unapply(to));
}
/*!
* Basic constructor. Inheriting children should call this constructor.
*
* \param rotation_matrix The rotation matrix used to enforce the infill angle
* \param result The resulting line segments (Each line segment is a Polygon with 2 points)
*/
ZigzagConnectorProcessor(const PointMatrix& rotation_matrix, Polygons& result)
: rotation_matrix(rotation_matrix)
, result(result)
{}
public:
/*!
* Handle the next vertex on the outer boundary.
* \param vertex The vertex
*/
virtual void registerVertex(const Point& vertex) = 0;
/*!
* Handle the next intersection between a scanline and the outer boundary.
*
* \param intersection The intersection
* \param scanline_is_even Whether the scanline was even
*/
virtual void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even) = 0;
/*!
* Handle the end of a polygon and prepare for the next.
* This function should reset all member variables.
*/
virtual void registerPolyFinished() = 0;
};
} // namespace cura
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_H
@@ -1,75 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "ZigzagConnectorProcessorConnectedEndPieces.h"
namespace cura
{
void ZigzagConnectorProcessorConnectedEndPieces::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
{
bool previous_scanline_is_even = last_scanline_is_even;
last_scanline_is_even = scanline_is_even;
bool this_scanline_is_even = last_scanline_is_even;
if (is_first_zigzag_connector)
{
first_zigzag_connector.push_back(intersection);
first_zigzag_connector_ends_in_even_scanline = this_scanline_is_even;
is_first_zigzag_connector = false;
}
else
{
if (previous_scanline_is_even)
{ // when a boundary segment starts in an even scanline it is either a normal zigzag connector or an endpiece, so it should be included anyway
addLine(last_connector_point, intersection);
}
else if (!previous_scanline_is_even && !this_scanline_is_even) // if we end an odd boundary in an odd segment
{ // add whole zigzag_connector (including the just obtained point)
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
{
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
}
addLine(zigzag_connector.back(), intersection);
zigzag_connector.clear();
}
}
zigzag_connector.clear(); // we're starting a new (odd) zigzag connector, so clear the old one
if (!this_scanline_is_even) // we are either in an end piece or an boundary segment starting in an odd scanline
{ // only when a boundary segment starts in an odd scanline it depends on whether it ends in an odd scanline for whether this segment should be included or not
zigzag_connector.push_back(intersection);
}
last_connector_point = intersection;
}
void ZigzagConnectorProcessorConnectedEndPieces::registerPolyFinished()
{
// write end segment if needed (first half of start/end-crossing segment)
if (!last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
{
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
{
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
}
}
// write begin segment if needed (second half of start/end-crossing segment)
if (last_scanline_is_even || (!last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
|| is_first_zigzag_connector)
{
for (unsigned int point_idx = 1; point_idx < first_zigzag_connector.size(); point_idx++)
{
addLine(first_zigzag_connector[point_idx - 1], first_zigzag_connector[point_idx]);
}
}
// reset member variables
is_first_zigzag_connector = true;
first_zigzag_connector_ends_in_even_scanline = true;
last_scanline_is_even = false;
first_zigzag_connector.clear();
zigzag_connector.clear();
}
} // namespace cura
@@ -1,27 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_CONNECTED_END_PIECES_H
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_CONNECTED_END_PIECES_H
#include "../utils/polygon.h"
#include "ZigzagConnectorProcessorEndPieces.h"
#include "../utils/intpoint.h"
namespace cura
{
class ZigzagConnectorProcessorConnectedEndPieces : public ZigzagConnectorProcessorEndPieces
{
public:
ZigzagConnectorProcessorConnectedEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
: ZigzagConnectorProcessorEndPieces(rotation_matrix, result)
{
}
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
void registerPolyFinished();
};
} // namespace cura
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_CONNECTED_END_PIECES_H
@@ -1,79 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "ZigzagConnectorProcessorDisconnectedEndPieces.h"
namespace cura
{
void ZigzagConnectorProcessorDisconnectedEndPieces::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
{
bool previous_scanline_is_even = last_scanline_is_even;
last_scanline_is_even = scanline_is_even;
bool this_scanline_is_even = last_scanline_is_even;
if (is_first_zigzag_connector)
{
first_zigzag_connector.push_back(intersection);
first_zigzag_connector_ends_in_even_scanline = this_scanline_is_even;
is_first_zigzag_connector = false;
}
else
{
if (previous_scanline_is_even && !this_scanline_is_even)
{ // if we left from an even scanline, but not if this is the line segment connecting that zigzag_connector to an even scanline
addLine(last_connector_point, intersection);
}
else if (!previous_scanline_is_even && !this_scanline_is_even) // if we end an odd boundary in an odd segment
{ // add whole oddBoundarySegment (including the just obtained point)
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
{
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
}
// skip the last segment to the [intersection]
zigzag_connector.clear();
}
}
zigzag_connector.clear(); // we're starting a new (odd) zigzag connector, so clear the old one
if (!this_scanline_is_even) // we are either in an end piece or an boundary segment starting in an odd scanline
{ // only when a boundary segment starts in an odd scanline it depends on whether it ends in an odd scanline for whether this segment should be included or not
zigzag_connector.push_back(intersection);
}
last_connector_point = intersection;
}
void ZigzagConnectorProcessorDisconnectedEndPieces::registerPolyFinished()
{
// write end segment if needed (first half of start/end-crossing segment)
if (!last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
{
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
{
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
}
}
// write begin segment if needed (second half of start/end-crossing segment)
if (last_scanline_is_even || is_first_zigzag_connector)
{
for (unsigned int point_idx = 1; point_idx < first_zigzag_connector.size() - 1; point_idx++) // -1 cause skipping very last line segment!
{
addLine(first_zigzag_connector[point_idx - 1], first_zigzag_connector[point_idx]);
}
}
// write very last line segment if needed
if (last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
{ // only add last element if boundary segment ends in odd scanline
addLine(first_zigzag_connector[first_zigzag_connector.size() - 2], first_zigzag_connector[first_zigzag_connector.size() - 1]);
}
// reset member variables
is_first_zigzag_connector = true;
first_zigzag_connector_ends_in_even_scanline = true;
last_scanline_is_even = false;
first_zigzag_connector.clear();
zigzag_connector.clear();
}
} // namespace cura
@@ -1,26 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_DISCONNECTED_END_PIECES_H
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_DISCONNECTED_END_PIECES_H
#include "../utils/polygon.h"
#include "ZigzagConnectorProcessorEndPieces.h"
namespace cura
{
class ZigzagConnectorProcessorDisconnectedEndPieces : public ZigzagConnectorProcessorEndPieces
{
public:
ZigzagConnectorProcessorDisconnectedEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
: ZigzagConnectorProcessorEndPieces(rotation_matrix, result)
{
}
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
void registerPolyFinished();
};
} // namespace cura
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_DISCONNECTED_END_PIECES_H
@@ -1,27 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "ZigzagConnectorProcessorEndPieces.h"
namespace cura
{
void ZigzagConnectorProcessorEndPieces::registerVertex(const Point& vertex)
{
if (is_first_zigzag_connector)
{
first_zigzag_connector.push_back(vertex);
}
else if (last_scanline_is_even)
{ // when a boundary segments starts in an even scanline it's either a normal zigzag connector or an endpiece to be included
// note that for ZigzagConnectorProcessorDisconnectedEndPieces only the last line segment from a boundary vertex to a scanline-boundary intersection is omitted
addLine(last_connector_point, vertex);
}
else
{ // it's yet unclear whether the line segment should be included, so we store it until we know
zigzag_connector.push_back(vertex);
}
last_connector_point = vertex;
}
} // namespace cura
@@ -1,32 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_END_PIECES_H
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_END_PIECES_H
#include "../utils/polygon.h"
#include "ActualZigzagConnectorProcessor.h"
namespace cura
{
class ZigzagConnectorProcessorEndPieces : public ActualZigzagConnectorProcessor
{
protected:
Point last_connector_point; //!< last registered boundary vertex or scanline-coundary intersection
ZigzagConnectorProcessorEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
: ActualZigzagConnectorProcessor(rotation_matrix, result)
, last_connector_point(0,0)
{
}
public:
void registerVertex(const Point& vertex);
};
} // namespace cura
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_END_PIECES_H
@@ -1,72 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#include "ZigzagConnectorProcessorNoEndPieces.h"
namespace cura
{
void ZigzagConnectorProcessorNoEndPieces::registerVertex(const Point& vertex)
{
if (is_first_zigzag_connector)
{
first_zigzag_connector.push_back(vertex);
}
else if (last_scanline_is_even)
{
zigzag_connector.push_back(vertex);
}
}
void ZigzagConnectorProcessorNoEndPieces::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
{
bool previous_scanline_is_even = last_scanline_is_even;
last_scanline_is_even = scanline_is_even;
bool this_scanline_is_even = last_scanline_is_even; // for conceptual clarity
if (is_first_zigzag_connector)
{
first_zigzag_connector.push_back(intersection);
first_zigzag_connector_ends_in_even_scanline = this_scanline_is_even;
is_first_zigzag_connector = false;
}
else
{
if (previous_scanline_is_even && !this_scanline_is_even)
{ // add whole zigzag_connector (including the just obtained point)
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
{
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
}
addLine(zigzag_connector.back(), intersection);
zigzag_connector.clear();
}
}
zigzag_connector.clear(); // we're starting a new zigzag connector, so clear the old one
if (this_scanline_is_even) // only boundary segments starting in an even segment are considered
{
zigzag_connector.push_back(intersection);
}
}
void ZigzagConnectorProcessorNoEndPieces::registerPolyFinished()
{
if (!is_first_zigzag_connector && last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
{ // only if it's a normal zigzag connector; not when the whole boundary didn't cross any scanlines
for (unsigned int point_idx = 1; point_idx < first_zigzag_connector.size() ; point_idx++)
{
addLine(first_zigzag_connector[point_idx - 1], first_zigzag_connector[point_idx]);
}
}
// reset member variables
is_first_zigzag_connector = true;
first_zigzag_connector_ends_in_even_scanline = true;
last_scanline_is_even = false;
first_zigzag_connector.clear();
zigzag_connector.clear();
}
} // namespace cura
@@ -1,29 +0,0 @@
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_NO_ENDPIECES_H
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_NO_ENDPIECES_H
#include "../utils/polygon.h"
#include "ActualZigzagConnectorProcessor.h"
#include "../utils/intpoint.h"
namespace cura
{
class ZigzagConnectorProcessorNoEndPieces : public ActualZigzagConnectorProcessor
{
public:
ZigzagConnectorProcessorNoEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
: ActualZigzagConnectorProcessor(rotation_matrix, result)
{
}
void registerVertex(const Point& vertex);
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
void registerPolyFinished();
};
} // namespace cura
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_NO_ENDPIECES_H
+12 -14
Ver Arquivo
@@ -1,10 +1,13 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "inset.h"
#include "polygonOptimizer.h"
#include "utils/polygonUtils.h"
namespace cura {
void generateInsets(SliceLayerPart* part, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
void generateInsets(SliceLayerPart* part, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters)
{
int combBoundaryInset = line_width_x/2; // hard coded value
part->combBoundery = part->outline.offset(-combBoundaryInset);
if (insetCount == 0)
{
part->insets.push_back(part->outline);
@@ -16,22 +19,16 @@ void generateInsets(SliceLayerPart* part, int wall_0_inset, int line_width_0, in
part->insets.push_back(Polygons());
if (i == 0)
{
PolygonUtils::offsetSafe(part->outline, -line_width_0 / 2 - wall_0_inset, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
offsetSafe(part->outline, - line_width_x/2, line_width_x, part->insets[i], avoidOverlappingPerimeters);
} else if (i == 1)
{
int offset_from_first_boundary_for_edge_of_outer_wall = -line_width_0 / 2; // the outer bounds of the perimeter gaps
// you might think this /\ should be (1): -line_width_0 / 2; or you might think it should be (2): -nozzle_size / 2
// (1): the volume extruded is the right volume; the infill gaps overlap more with the outer wall
// (2): the outer wall already fills up extra space due to the fact that the nozzle hole overlaps with a part inside the outer wall
PolygonUtils::offsetSafe(part->insets[0], -line_width_0 / 2 + wall_0_inset - line_width_x / 2, offset_from_first_boundary_for_edge_of_outer_wall, line_width_x, part->insets[1], &part->perimeterGaps, avoidOverlappingPerimeters);
offsetExtrusionWidth(part->insets[i-1], true, line_width_0, part->insets[i], &part->perimeterGaps, avoidOverlappingPerimeters);
} else
{
PolygonUtils::offsetExtrusionWidth(part->insets[i-1], true, line_width_x, part->insets[i], &part->perimeterGaps, avoidOverlappingPerimeters);
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();
optimizePolygons(part->insets[i]);
if (part->insets[i].size() < 1)
{
part->insets.pop_back();
@@ -40,11 +37,12 @@ void generateInsets(SliceLayerPart* part, int wall_0_inset, int line_width_0, in
}
}
void generateInsets(SliceLayer* layer, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
void generateInsets(SliceLayer* layer, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters)
{
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
{
generateInsets(&layer->parts[partNr], wall_0_inset, line_width_0, line_width_x, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
generateInsets(&layer->parts[partNr], line_width_0, line_width_x, insetCount, avoidOverlappingPerimeters);
}
//Remove the parts which did not generate an inset. As these parts are too small to print,
+6 -13
Ver Arquivo
@@ -11,30 +11,23 @@ namespace cura
* Generates the insets / perimeters for a single layer part.
*
* \param part The part for which to generate the insets.
* \param wall_0_inset The offset applied to the outer wall
* \param line_width_0 line width of the outer wall
* \param line_width_x line width of other walls
* \param line_width_0 Line width of the outer wall
* \param line_width_x Line width of other walls
* \param insetCount The number of insets to to generate
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
*/
void generateInsets(SliceLayerPart* part, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
void generateInsets(SliceLayerPart* part, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters);
/*!
* Generates the insets / perimeters for all parts in a layer.
*
* Note that the second inset gets offsetted by \p line_width_0 instead of the first,
* which leads to better results for a smaller \p line_width_0 than \p line_width_x and when printing the outer wall last.
*
* \param layer The layer for which to generate the insets.
* \param wall_0_inset The offset applied to the outer wall
* \param line_width_0 line width of the outer wall
* \param line_width_x line width of other walls
* \param line_width_0 Line width of the outer wall
* \param line_width_x Line width of other walls
* \param insetCount The number of insets to to generate
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
*/
void generateInsets(SliceLayer* layer, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
void generateInsets(SliceLayer* layer, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters);
}//namespace cura
+26 -34
Ver Arquivo
@@ -1,10 +1,8 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include <stdio.h>
#include "LayerPart.h"
#include "../settings.h"
#include "../Progress.h"
#include "../utils/SVG.h" // debug output
#include "layerPart.h"
#include "settings.h"
/*
The layer-part creation step is the first step in creating actual useful data for 3D printing.
@@ -13,7 +11,7 @@ each of these groups is called a "part", which sometimes are also known as "isla
isolated areas in the 2D layer with possible holes.
Creating "parts" is an important step, as all elements in a single part should be printed before going to another part.
And all every bit inside a single part can be printed without the nozzle leaving the boundary of this part.
And all every bit inside a single part can be printed without the nozzle leaving the boundery of this part.
It's also the first step that stores the result in the "data storage" so all other steps can access it.
*/
@@ -22,7 +20,7 @@ namespace cura {
void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool union_layers, bool union_all_remove_holes)
{
storageLayer.openPolyLines = layer->openPolylines;
storageLayer.openLines = layer->openPolygons;
if (union_all_remove_holes)
{
@@ -33,15 +31,16 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
}
}
std::vector<PolygonsPart> result;
std::vector<Polygons> result;
result = layer->polygonList.splitIntoParts(union_layers || union_all_remove_holes);
for(unsigned int i=0; i<result.size(); i++)
{
storageLayer.parts.emplace_back();
storageLayer.parts.push_back(SliceLayerPart());
storageLayer.parts[i].outline = result[i];
storageLayer.parts[i].boundaryBox.calculate(storageLayer.parts[i].outline);
}
}
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++)
@@ -50,48 +49,41 @@ void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_laye
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);
logProgress("layerparts", layer_nr + 1, slicer->layers.size());
}
}
void layerparts2HTML(SliceDataStorage& storage, const char* filename, bool all_layers, int layer_nr)
void dumpLayerparts(SliceDataStorage& storage, const char* filename)
{
FILE* out = fopen(filename, "w");
fprintf(out, "<!DOCTYPE html><html><body>");
Point3 modelSize = storage.model_size;
Point3 modelMin = storage.model_min;
Point model_min_2d = Point(modelMin.x, modelMin.y);
Point model_max_2d = Point(modelSize.x, modelSize.y) + model_min_2d;
AABB aabb(model_min_2d, model_max_2d);
SVG svg(filename, aabb);
for(SliceMeshStorage& mesh : storage.meshes)
{
for(unsigned int layer_idx = 0; layer_idx < mesh.layers.size(); layer_idx++)
for(SliceLayer& layer : mesh.layers)
{
if (!(all_layers || int(layer_idx) == layer_nr)) { continue; }
SliceLayer& layer = mesh.layers[layer_idx];
// fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n");
fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n");
for(SliceLayerPart& part : layer.parts)
{
svg.writeAreas(part.outline);
svg.writePoints(part.outline);
// for(unsigned int j=0;j<part.outline.size();j++)
// {
// fprintf(out, "<polygon points=\"");
// for(unsigned int k=0;k<part.outline[j].size();k++)
// fprintf(out, "%f,%f ", float(part.outline[j][k].X - modelMin.x)/modelSize.x*500, float(part.outline[j][k].Y - modelMin.y)/modelSize.y*500);
// if (j == 0)
// fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
// else
// fprintf(out, "\" style=\"fill:red; stroke:black;stroke-width:1\" />\n");
// }
for(unsigned int j=0;j<part.outline.size();j++)
{
fprintf(out, "<polygon points=\"");
for(unsigned int k=0;k<part.outline[j].size();k++)
fprintf(out, "%f,%f ", float(part.outline[j][k].X - modelMin.x)/modelSize.x*500, float(part.outline[j][k].Y - modelMin.y)/modelSize.y*500);
if (j == 0)
fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
else
fprintf(out, "\" style=\"fill:red; stroke:black;stroke-width:1\" />\n");
}
}
// fprintf(out, "</svg>\n");
fprintf(out, "</svg>\n");
}
}
fprintf(out, "</body></html>");
fclose(out);
}
}//namespace cura
+7 -8
Ver Arquivo
@@ -1,10 +1,9 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef SLICER_LAYERPART_H
#define SLICER_LAYERPART_H
#ifndef LAYERPART_H
#define LAYERPART_H
#include "../sliceDataStorage.h"
#include "Slicer.h"
#include "../commandSocket.h"
#include "sliceDataStorage.h"
#include "slicer.h"
/*
The layer-part creation step is the first step in creating actual useful data for 3D printing.
@@ -13,7 +12,7 @@ each of these groups is called a "part", which sometimes are also known as "isla
isolated areas in the 2D layer with possible holes.
Creating "parts" is an important step, as all elements in a single part should be printed before going to another part.
And all every bit inside a single part can be printed without the nozzle leaving the boundary of this part.
And all every bit inside a single part can be printed without the nozzle leaving the boundery of this part.
It's also the first step that stores the result in the "data storage" so all other steps can access it.
*/
@@ -24,8 +23,8 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes);
void layerparts2HTML(SliceDataStorage& storage, const char* filename, bool all_layers = true, int layer_nr = -1);
void dumpLayerparts(SliceDataStorage& storage, const char* filename);
}//namespace cura
#endif//SLICER_LAYERPART_H
#endif//LAYERPART_H
+137 -265
Ver Arquivo
@@ -14,38 +14,30 @@
#include "utils/gettime.h"
#include "utils/logoutput.h"
#include "utils/string.h"
#include "sliceDataStorage.h"
#include "FffProcessor.h"
#include "modelFile/modelFile.h"
#include "settings.h"
#include "settingRegistry.h"
#include "multiVolumes.h"
#include "polygonOptimizer.h"
#include "slicer.h"
#include "layerPart.h"
#include "inset.h"
#include "skin.h"
#include "infill.h"
#include "bridge.h"
#include "support.h"
#include "pathOrderOptimizer.h"
#include "skirt.h"
#include "raft.h"
#include "comb.h"
#include "gcodeExport.h"
#include "fffProcessor.h"
namespace cura
{
void print_usage()
{
cura::logError("\n");
cura::logError("usage:\n");
cura::logError("CuraEngine help\n");
cura::logError("\tShow this help message\n");
cura::logError("\n");
cura::logError("CuraEngine connect <host>[:<port>] [-j <settings.json>]\n");
cura::logError(" --connect <host>[:<port>]\n\tConnect to <host> via a command socket, \n\tinstead of passing information via the command line\n");
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults\n");
cura::logError("\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.json file to register all settings and their defaults.\n");
cura::logError(" -s <setting>=<value>\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n");
cura::logError(" -l <model_file>\n\tLoad an STL model. \n");
cura::logError(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n");
cura::logError(" -e\n\tAdd a new extruder train.\n");
cura::logError(" --next\n\tGenerate gcode for the previously supplied mesh group and append that to \n\tthe gcode of further models for one-at-a-time printing.\n");
cura::logError(" -o <output_file>\n\tSpecify a file to which to write the generated gcode.\n");
cura::logError("\n");
cura::logError("The settings are appended to the last supplied object:\n");
cura::logError("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e [extruder train settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
cura::logError("\n");
cura::logError("usage: CuraEngine [-h] [-v] [-m 3x3matrix] [-c <config file>] [-s <settingkey>=<value>] -o <output.gcode> <model.stl>\n");
}
//Signal handler for a "floating point exception", which can also be integer division by zero errors.
@@ -56,222 +48,6 @@ void signal_FPE(int n)
exit(1);
}
void print_call(int argc, char **argv)
{
cura::logError("Command called:\n");
for (int idx= 0; idx < argc; idx++)
cura::logError("%s ", argv[idx]);
cura::logError("\n");
}
void connect(int argc, char **argv)
{
CommandSocket::instantiate();
std::string ip;
int port = 49674;
std::string ip_port(argv[2]);
if (ip_port.find(':') != std::string::npos)
{
ip = ip_port.substr(0, ip_port.find(':'));
port = std::stoi(ip_port.substr(ip_port.find(':') + 1).data());
}
for(int argn = 3; argn < argc; argn++)
{
char* str = argv[argn];
if (str[0] == '-')
{
for(str++; *str; str++)
{
switch(*str)
{
case 'v':
cura::increaseVerboseLevel();
break;
case 'j':
argn++;
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
{
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
}
break;
default:
cura::logError("Unknown option: %c\n", *str);
print_call(argc, argv);
print_usage();
break;
}
}
}
}
CommandSocket::getInstance()->connect(ip, port);
}
void slice(int argc, char **argv)
{
FffProcessor::getInstance()->time_keeper.restart();
FMatrix3x3 transformation; // the transformation applied to a model when loaded
MeshGroup* meshgroup = new MeshGroup(FffProcessor::getInstance());
int extruder_train_nr = 0;
SettingsBase* last_extruder_train = meshgroup->createExtruderTrain(0);
SettingsBase* last_settings_object = FffProcessor::getInstance();
for(int argn = 2; argn < argc; argn++)
{
char* str = argv[argn];
if (str[0] == '-')
{
if (str[1] == '-')
{
if (stringcasecompare(str, "--next") == 0)
{
try {
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
meshgroup->finalize();
log("Loaded from disk in %5.3fs\n", FffProcessor::getInstance()->time_keeper.restart());
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
{ // initialize remaining extruder trains and load the defaults
meshgroup->getExtruderTrain(extruder_nr)->setExtruderTrainDefaults(extruder_nr); // create new extruder train objects or use already existing ones
}
//start slicing
FffProcessor::getInstance()->processMeshGroup(meshgroup);
// initialize loading of new meshes
FffProcessor::getInstance()->time_keeper.restart();
delete meshgroup;
meshgroup = new MeshGroup(FffProcessor::getInstance());
last_extruder_train = meshgroup->createExtruderTrain(0);
last_settings_object = meshgroup;
}catch(...){
cura::logError("Unknown exception\n");
exit(1);
}
}else{
cura::logError("Unknown option: %s\n", str);
}
}else{
for(str++; *str; str++)
{
switch(*str)
{
case 'v':
cura::increaseVerboseLevel();
break;
case 'p':
cura::enableProgressLogging();
break;
case 'j':
argn++;
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
{
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
}
break;
case 'e':
str++;
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;
break;
case 'l':
argn++;
log("Loading %s from disk...\n", argv[argn]);
// transformation = // TODO: get a transformation from somewhere
if (!loadMeshIntoMeshGroup(meshgroup, argv[argn], transformation, last_extruder_train))
{
logError("Failed to load model: %s\n", argv[argn]);
}
else
{
last_settings_object = meshgroup->meshes.back();
}
break;
case 'o':
argn++;
if (!FffProcessor::getInstance()->setTargetFile(argv[argn]))
{
cura::logError("Failed to open %s for output.\n", argv[argn]);
exit(1);
}
break;
case 'g':
last_settings_object = meshgroup;
case 's':
{
//Parse the given setting and store it.
argn++;
char* valuePtr = strchr(argv[argn], '=');
if (valuePtr)
{
*valuePtr++ = '\0';
last_settings_object->setSetting(argv[argn], valuePtr);
}
}
break;
default:
cura::logError("Unknown option: %c\n", *str);
print_call(argc, argv);
print_usage();
exit(1);
break;
}
}
}
}
else
{
cura::logError("Unknown option: %s\n", argv[argn]);
print_call(argc, argv);
print_usage();
exit(1);
}
}
int extruder_count = FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count");
for (extruder_train_nr = 0; extruder_train_nr < extruder_count; extruder_train_nr++)
{ // initialize remaining extruder trains and load the defaults
meshgroup->createExtruderTrain(extruder_train_nr)->setExtruderTrainDefaults(extruder_train_nr); // create new extruder train objects or use already existing ones
}
#ifndef DEBUG
try {
#endif
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
meshgroup->finalize();
log("Loaded from disk in %5.3fs\n", FffProcessor::getInstance()->time_keeper.restart());
//start slicing
FffProcessor::getInstance()->processMeshGroup(meshgroup);
#ifndef DEBUG
}catch(...){
cura::logError("Unknown exception\n");
exit(1);
}
#endif
//Finalize the processor, this adds the end.gcode. And reports statistics.
FffProcessor::getInstance()->finalize();
delete meshgroup;
}
}//namespace cura
using namespace cura;
int main(int argc, char **argv)
@@ -286,10 +62,9 @@ int main(int argc, char **argv)
signal(SIGFPE, signal_FPE);
#endif
Progress::init();
logCopyright("\n");
fffProcessor processor;
std::vector<std::string> files;
logCopyright("Cura_SteamEngine version %s\n", VERSION);
logCopyright("Copyright (C) 2014 David Braam\n");
logCopyright("\n");
@@ -306,33 +81,130 @@ int main(int argc, char **argv)
logCopyright("You should have received a copy of the GNU Affero General Public License\n");
logCopyright("along with this program. If not, see <http://www.gnu.org/licenses/>.\n");
CommandSocket* commandSocket = NULL;
std::string ip;
int port = 49674;
if (argc < 2)
for(int argn = 1; argn < argc; argn++)
{
print_usage();
exit(1);
char* str = argv[argn];
if (str[0] == '-')
{
if (str[1] == '-')
{
if (stringcasecompare(str, "--connect") == 0)
{
commandSocket = new CommandSocket(&processor);
std::string ip_port(argv[argn + 1]);
if (ip_port.find(':') != std::string::npos)
{
ip = ip_port.substr(0, ip_port.find(':'));
port = std::stoi(ip_port.substr(ip_port.find(':') + 1).data());
}
argn += 1;
}
else if (stringcasecompare(str, "--") == 0)
{
try {
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
if (files.size() > 0)
processor.processFiles(files);
files.clear();
}catch(...){
cura::logError("Unknown exception\n");
exit(1);
}
break;
}else{
cura::logError("Unknown option: %s\n", str);
}
}else{
for(str++; *str; str++)
{
switch(*str)
{
case 'h':
print_usage();
exit(1);
case 'v':
cura::increaseVerboseLevel();
break;
case 'j':
argn++;
if (!SettingRegistry::getInstance()->loadJSON(argv[argn]))
{
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
}
break;
case 'p':
cura::enableProgressLogging();
break;
case 'o':
argn++;
if (!processor.setTargetFile(argv[argn]))
{
cura::logError("Failed to open %s for output.\n", argv[argn]);
exit(1);
}
break;
case 's':
{
//Parse the given setting and store it.
argn++;
char* valuePtr = strchr(argv[argn], '=');
if (valuePtr)
{
*valuePtr++ = '\0';
processor.setSetting(argv[argn], valuePtr);
}
}
break;
default:
cura::logError("Unknown option: %c\n", *str);
break;
}
}
}
}else{
files.push_back(argv[argn]);
}
}
if (stringcasecompare(argv[1], "connect") == 0)
if (!SettingRegistry::getInstance()->settingsLoaded())
{
connect(argc, argv);
}
else if (stringcasecompare(argv[1], "slice") == 0)
{
slice(argc, argv);
//If no json file has been loaded, try to load the default.
if (!SettingRegistry::getInstance()->loadJSON("fdmprinter.json"))
{
logError("ERROR: Failed to load json file: fdmprinter.json\n");
}
}
else if (stringcasecompare(argv[1], "help") == 0)
if(commandSocket)
{
print_usage();
exit(0);
commandSocket->connect(ip, port);
}
else
{
cura::logError("Unknown command: %s\n", argv[1]);
print_call(argc, argv);
print_usage();
exit(1);
#ifndef DEBUG
try {
#endif
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
if (files.size() > 0)
processor.processFiles(files);
#ifndef DEBUG
}catch(...){
cura::logError("Unknown exception\n");
exit(1);
}
#endif
//Finalize the processor, this adds the end.gcode. And reports statistics.
processor.finalize();
}
return 0;
}
}
+28 -48
Ver Arquivo
@@ -1,35 +1,24 @@
#include "mesh.h"
#include "utils/logoutput.h"
namespace cura
{
const int vertex_meld_distance = MM2INT(0.03);
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);
}
Mesh::Mesh(SettingsBaseVirtual* parent)
Mesh::Mesh(SettingsBase* parent)
: SettingsBase(parent)
{
}
bool Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
void Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
{
int vi0 = findIndexOfVertex(v0);
int vi1 = findIndexOfVertex(v1);
int vi2 = findIndexOfVertex(v2);
return addFace(vi0, vi1, vi2);
}
bool Mesh::addFace(int vi0, int vi1, int vi2)
{
if (vi0 == vi1 || vi1 == vi2 || vi0 == vi2)
{
// the face has two vertices which get assigned the same location. Don't add the face.
return false;
}
if (vi0 == vi1 || vi1 == vi2 || vi0 == vi2) return; // the face has two vertices which get assigned the same location. Don't add the face.
int idx = faces.size(); // index of face to be added
faces.emplace_back();
@@ -40,8 +29,6 @@ bool Mesh::addFace(int vi0, int vi1, int vi2)
vertices[face.vertex_index[0]].connected_faces.push_back(idx);
vertices[face.vertex_index[1]].connected_faces.push_back(idx);
vertices[face.vertex_index[2]].connected_faces.push_back(idx);
return true;
}
void Mesh::clear()
@@ -66,24 +53,34 @@ void Mesh::finish()
}
}
Point3 Mesh::min() const
Point3 Mesh::min()
{
return aabb.min;
if (vertices.size() < 1)
return Point3(0, 0, 0);
Point3 ret = vertices[0].p;
for(unsigned int i=0; i<vertices.size(); i++)
{
ret.x = std::min(ret.x, vertices[i].p.x);
ret.y = std::min(ret.y, vertices[i].p.y);
ret.z = std::min(ret.z, vertices[i].p.z);
}
return ret;
}
Point3 Mesh::max() const
Point3 Mesh::max()
{
return aabb.max;
if (vertices.size() < 1)
return Point3(0, 0, 0);
Point3 ret = vertices[0].p;
for(unsigned int i=0; i<vertices.size(); i++)
{
ret.x = std::max(ret.x, vertices[i].p.x);
ret.y = std::max(ret.y, vertices[i].p.y);
ret.z = std::max(ret.z, vertices[i].p.z);
}
return ret;
}
void Mesh::addVertex(const Point3& v)
{
uint32_t hash = pointHash(v);
vertex_hash_map[hash].push_back(vertices.size());
vertices.emplace_back(v);
aabb.include(v);
}
int Mesh::findIndexOfVertex(const Point3& v)
int Mesh::findIndexOfVertex(Point3& v)
{
uint32_t hash = pointHash(v);
@@ -96,9 +93,6 @@ int Mesh::findIndexOfVertex(const Point3& v)
}
vertex_hash_map[hash].push_back(vertices.size());
vertices.emplace_back(v);
aabb.include(v);
return vertices.size() - 1;
}
@@ -126,7 +120,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
@@ -197,17 +191,3 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
if (bestIdx < 0) cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx);
return bestIdx;
}
bool Mesh::registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result)
{
// do nothing for a non-textured mesh
return false;
}
float Mesh::getColor(MatCoord bitmap_coord)
{
return 0.0f;
}
}//namespace cura
+9 -52
Ver Arquivo
@@ -2,11 +2,7 @@
#define MESH_H
#include "settings.h"
#include "utils/AABB.h"
#include "MatSegment.h"
namespace cura
{
/*!
Vertex type to be used in a Mesh.
@@ -18,7 +14,7 @@ public:
Point3 p; //!< location of the vertex
std::vector<uint32_t> connected_faces; //!< list of the indices of connected faces
MeshVertex(Point3 p) : p(p) {connected_faces.reserve(8);} //!< doesn't set connected_faces
MeshVertex(Point3 p) : p(p) {} //!< doesn't set connected_faces
};
/*! A MeshFace is a 3 dimensional model triangle with 3 points. These points are already converted to integers
@@ -58,68 +54,29 @@ See MeshFace for the specifics of how/when faces are connected.
class Mesh : public SettingsBase // inherits settings
{
//! The vertex_hash_map stores a index reference of each vertex for the hash of that location. Allows for quick retrieval of points with the same location.
std::unordered_map<uint32_t, std::vector<uint32_t> > vertex_hash_map;
AABB3D aabb;
std::map<uint32_t, std::vector<uint32_t> > vertex_hash_map;
public:
std::vector<MeshVertex> vertices;//!< list of all vertices in the mesh
std::vector<MeshFace> faces; //!< list of all faces in the mesh
Mesh(SettingsBaseVirtual* parent); //!< initializes the settings
virtual ~Mesh() {} //!< Destructor
/*!
* add a face to the mesh without settings it's connected_faces.
*
* Don't add a face when the surface is zero mm^2
*
* \return whether a face has been added
*/
bool addFace(Point3& v0, Point3& v1, Point3& v2);
/*!
* add a face to the mesh without settings it's connected_faces.
*
* Don't add a face when the surface is zero mm^2
*
* \return whether a face has been added
*/
bool addFace(int vi0, int vi1, int vi2);
void addVertex(const Point3& v);
Mesh(SettingsBase* parent); //!< initializes the settings
void addFace(Point3& v0, Point3& v1, Point3& v2); //!< add a face to the mesh without settings it's connected_faces.
void clear(); //!< clears all data
void finish(); //!< complete the model : set the connected_face_index fields of the faces.
Point3 min() const; //!< min (in x,y and z) vertex of the bounding box
Point3 max() const; //!< max (in x,y and z) vertex of the bounding box
/*!
* Offset the whole mesh (all vertices and the bounding box).
* \param offset The offset byu which to offset the whole mesh.
*/
void offset(Point3 offset)
{
if (offset == Point3(0,0,0)) { return; }
for(MeshVertex& v : vertices)
v.p += offset;
aabb.offset(offset);
}
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
/*!
* \return Whether a texture line segment has been created
*/
virtual bool registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result);
virtual float getColor(MatCoord bitmap_coord);
private:
int findIndexOfVertex(const Point3& v); //!< find index of vertex close to the given point, or create a new vertex and return its index.
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
#endif//MESH_H
+150
Ver Arquivo
@@ -0,0 +1,150 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include "modelFile.h"
#include "../utils/logoutput.h"
#include "../utils/string.h"
FILE* binaryMeshBlob = nullptr;
/* Custom fgets function to support Mac line-ends in Ascii STL files. OpenSCAD produces this when used on Mac */
void* fgets_(char* ptr, size_t len, FILE* f)
{
while(len && fread(ptr, 1, 1, f) > 0)
{
if (*ptr == '\n' || *ptr == '\r')
{
*ptr = '\0';
return ptr;
}
ptr++;
len--;
}
return nullptr;
}
bool loadModelSTL_ascii(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rt");
char buffer[1024];
FPoint3 vertex;
int n = 0;
Point3 v0(0,0,0), v1(0,0,0), v2(0,0,0);
while(fgets_(buffer, sizeof(buffer), f))
{
if (sscanf(buffer, " vertex %f %f %f", &vertex.x, &vertex.y, &vertex.z) == 3)
{
n++;
switch(n)
{
case 1:
v0 = matrix.apply(vertex);
break;
case 2:
v1 = matrix.apply(vertex);
break;
case 3:
v2 = matrix.apply(vertex);
mesh->addFace(v0, v1, v2);
n = 0;
break;
}
}
}
fclose(f);
mesh->finish();
return true;
}
bool loadModelSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "rb");
char buffer[80];
uint32_t faceCount;
//Skip the header
if (fread(buffer, 80, 1, f) != 1)
{
fclose(f);
return false;
}
//Read the face count
if (fread(&faceCount, sizeof(uint32_t), 1, f) != 1)
{
fclose(f);
return false;
}
//For each face read:
//float(x,y,z) = normal, float(X,Y,Z)*3 = vertexes, uint16_t = flags
for(unsigned int i=0;i<faceCount;i++)
{
if (fread(buffer, sizeof(float) * 3, 1, f) != 1)
{
fclose(f);
return false;
}
float v[9];
if (fread(v, sizeof(float) * 9, 1, f) != 1)
{
fclose(f);
return false;
}
Point3 v0 = matrix.apply(FPoint3(v[0], v[1], v[2]));
Point3 v1 = matrix.apply(FPoint3(v[3], v[4], v[5]));
Point3 v2 = matrix.apply(FPoint3(v[6], v[7], v[8]));
mesh->addFace(v0, v1, v2);
if (fread(buffer, sizeof(uint16_t), 1, f) != 1)
{
fclose(f);
return false;
}
}
fclose(f);
mesh->finish();
return true;
}
bool loadModelSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
{
FILE* f = fopen(filename, "r");
char buffer[6];
if (f == nullptr)
return false;
if (fread(buffer, 5, 1, f) != 1)
{
fclose(f);
return false;
}
fclose(f);
buffer[5] = '\0';
if (stringcasecompare(buffer, "solid") == 0)
{
bool load_success = loadModelSTL_ascii(mesh, filename, matrix);
if (!load_success)
return false;
// This logic is used to handle the case where the file starts with
// "solid" but is a binary file.
if (mesh->faces.size() < 1)
{
mesh->clear();
return loadModelSTL_binary(mesh, filename, matrix);
}
return true;
}
return loadModelSTL_binary(mesh, filename, matrix);
}
bool loadMeshFromFile(PrintObject* object, const char* filename, FMatrix3x3& matrix)
{
const char* ext = strrchr(filename, '.');
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
{
object->meshes.emplace_back(object);
return loadModelSTL(&object->meshes[object->meshes.size()-1], filename, matrix);
}
return false;
}
+98
Ver Arquivo
@@ -0,0 +1,98 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef MODELFILE_H
#define MODELFILE_H
/**
modelFile contains the model loaders for the slicer. The model loader turns any format that it can read into a list of triangles with 3 X/Y/Z points.
The format returned is a Model class with an array of faces, which have integer points with a resolution of 1 micron. Giving a maximum object size of 4 meters.
**/
#include "../mesh.h"
//A PrintObject is a 3D model with 1 or more 3D meshes.
class PrintObject : public SettingsBase
{
public:
std::vector<Mesh> meshes;
PrintObject(SettingsBase* settings_base)
: SettingsBase(settings_base)
{
}
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()
{
for(Mesh& m : meshes)
m.clear();
}
void offset(Point3 offset)
{
for(Mesh& m : meshes)
for(MeshVertex& v : m.vertices)
v.p += offset;
}
void finalize()
{
// If a mesh position was given, put the mesh at this position in 3D space.
if (hasSetting("mesh_position_x") || hasSetting("mesh_position_y") || hasSetting("mesh_position_z"))
{
Point3 object_min = min();
Point3 object_max = max();
Point3 object_size = object_max - object_min;
Point3 object_offset = Point3(-object_min.x - object_size.x / 2, -object_min.y - object_size.y / 2, -object_min.z);
if (hasSetting("mesh_position_x"))
object_offset.x += getSettingInMicrons("mesh_position_x");
if (hasSetting("mesh_position_y"))
object_offset.y += getSettingInMicrons("mesh_position_y");
if (hasSetting("mesh_position_z"))
object_offset.z += getSettingInMicrons("mesh_position_z");
offset(object_offset);
}
//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.
if (hasSetting("machine_center_is_zero") && !getSettingBoolean("machine_center_is_zero"))
{
Point3 object_offset = Point3(0, 0, 0);
if (hasSetting("machine_width"))
object_offset.x = getSettingInMicrons("machine_width") / 2;
if (hasSetting("machine_depth"))
object_offset.y = getSettingInMicrons("machine_depth") / 2;
offset(object_offset);
}
}
};
bool loadMeshFromFile(PrintObject* object, const char* filename, FMatrix3x3& matrix);
#endif//MODELFILE_H
+58
Ver Arquivo
@@ -0,0 +1,58 @@
#include "multiVolumes.h"
namespace cura {
void carveMultipleVolumes(std::vector<SliceMeshStorage> &volumes)
{
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
for(unsigned int idx=0; idx < volumes.size(); idx++)
{
for(unsigned int idx2=0; idx2<idx; idx2++)
{
for(unsigned int layerNr=0; layerNr < volumes[idx].layers.size(); layerNr++)
{
SliceLayer* layer1 = &volumes[idx].layers[layerNr];
SliceLayer* layer2 = &volumes[idx2].layers[layerNr];
for(unsigned int p1 = 0; p1 < layer1->parts.size(); p1++)
{
for(unsigned int p2 = 0; p2 < layer2->parts.size(); p2++)
{
layer1->parts[p1].outline = layer1->parts[p1].outline.difference(layer2->parts[p2].outline);
}
}
}
}
}
}
//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<SliceMeshStorage> &volumes, int overlap)
{
if (volumes.size() < 2 || overlap <= 0) return;
for(unsigned int layerNr=0; layerNr < volumes[0].layers.size(); layerNr++)
{
Polygons fullLayer;
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
{
SliceLayer* layer1 = &volumes[volIdx].layers[layerNr];
for(unsigned int p1 = 0; p1 < layer1->parts.size(); p1++)
{
fullLayer = fullLayer.unionPolygons(layer1->parts[p1].outline.offset(20)); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
}
}
fullLayer = fullLayer.offset(-20); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
{
SliceLayer* layer1 = &volumes[volIdx].layers[layerNr];
for(unsigned int p1 = 0; p1 < layer1->parts.size(); p1++)
{
layer1->parts[p1].outline = fullLayer.intersection(layer1->parts[p1].outline.offset(overlap / 2));
}
}
}
}
}//namespace cura
+17
Ver Arquivo
@@ -0,0 +1,17 @@
#ifndef MULTIVOLUMES_H
#define MULTIVOLUMES_H
#include "sliceDataStorage.h"
/* This file contains code to help fixing up and changing layers that are build from multiple volumes. */
namespace cura {
void carveMultipleVolumes(std::vector<SliceMeshStorage> &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<SliceMeshStorage> &meshes, int overlap);
}//namespace cura
#endif//MULTIVOLUMES_H
+115 -138
Ver Arquivo
@@ -1,8 +1,9 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include <map>
#include "pathOrderOptimizer.h"
#include "utils/logoutput.h"
#include "utils/BucketGrid2D.h"
#include "utils/linearAlg2D.h"
#define INLINE static inline
@@ -16,16 +17,17 @@ 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
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,113 +39,74 @@ 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 i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// 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 i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++)
{
if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons
{
if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons
continue;
}
assert (polygons[poly_idx].size() != 2);
assert (polygons[i_polygon].size() != 2);
float dist = vSize2f(polygons[poly_idx][polyStart[poly_idx]] - prev_point);
float dist = vSize2f(polygons[i_polygon][polyStart[i_polygon]] - prev_point);
if (dist < bestDist)
{
best_poly_idx = poly_idx;
best = i_polygon;
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 point_idx = getPolyStart(prev_point, poly_idx);
polyStart[poly_idx] = point_idx;
prev_point = polygons[poly_idx][point_idx];
int i_polygon = polyOrder[n];
int best = getClosestPointInPolygon(prev_point, i_polygon);
polyStart[i_polygon] = best;
prev_point = polygons[i_polygon][best];
}
}
int PathOrderOptimizer::getPolyStart(Point prev_point, int poly_idx)
inline int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int i_polygon)
{
switch (type)
PolygonRef poly = polygons[i_polygon];
int best = -1;
float bestDist = std::numeric_limits<float>::infinity();
bool orientation = poly.orientation();
for(unsigned int i_point=0 ; i_point<poly.size() ; i_point++)
{
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++)
{
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;
}
p0 = p1;
}
return best_point_idx;
}
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++)
{
if (poly[point_idx].Y > best_y)
{
best_point_idx = point_idx;
best_y = poly[point_idx].Y;
best = i_point;
bestDist = dist;
}
}
return best_point_idx;
return best;
}
@@ -152,118 +115,132 @@ int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
*/
void LineOrderOptimizer::optimize()
{
int gridSize = 5000; // the size of the cells in the hash grid. TODO
int gridSize = 5000; // the size of the cells in the hash grid.
BucketGrid2D<unsigned int> line_bucket_grid(gridSize);
bool picked[polygons.size()];
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) /// find closest point to initial starting point within each polygon +initialize picked
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_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
int best = -1;
float bestDist = std::numeric_limits<float>::infinity();
PolygonRef poly = polygons[i_polygon];
for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point from polygon
{
float dist = vSize2f(poly[point_idx] - startPoint);
if (dist < best_point_dist)
float dist = vSize2f(poly[i_point] - startPoint);
if (dist < bestDist)
{
best_point_idx = point_idx;
best_point_dist = dist;
best = i_point;
bestDist = dist;
}
}
polyStart.push_back(best_point_idx);
polyStart.push_back(best);
assert(poly.size() == 2);
line_bucket_grid.insert(poly[0], poly_idx);
line_bucket_grid.insert(poly[1], poly_idx);
line_bucket_grid.insert(poly[0], i_polygon);
line_bucket_grid.insert(poly[1], i_polygon);
}
Point incoming_perpundicular_normal(0, 0);
Point incommingPerpundicularNormal(0, 0);
Point prev_point = startPoint;
for (unsigned int order_idx = 0; order_idx < polygons.size(); order_idx++) /// actual path order optimizer
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer
{
int best_line_idx = -1;
float best_score = std::numeric_limits<float>::infinity(); // distance score for the best next line
int best = -1;
float bestDist = std::numeric_limits<float>::infinity();
for(unsigned int close_line_poly_idx : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
for(unsigned int i_close_line_polygon : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
{
if (picked[close_line_poly_idx] || polygons[close_line_poly_idx].size() < 1)
{
if (picked[i_close_line_polygon] || polygons[i_close_line_polygon].size() < 1)
continue;
}
updateBestLine(close_line_poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
checkIfLineIsBest(i_close_line_polygon, best, bestDist, prev_point, incommingPerpundicularNormal);
}
if (best_line_idx == -1) /// if single-line-polygon hasn't been found yet
if (best == -1) /// if single-line-polygon hasn't been found yet
{
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++)
{
if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons
{
if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons
continue;
}
assert(polygons[poly_idx].size() == 2);
assert(polygons[i_polygon].size() == 2);
updateBestLine(poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
checkIfLineIsBest(i_polygon, best, bestDist, prev_point, incommingPerpundicularNormal);
}
}
if (best_line_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
{
PolygonRef best_line = polygons[best_line_idx];
assert(best_line.size() == 2);
assert(polygons[best].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));
int endIdx = polyStart[best] * -1 + 1; /// 1 -> 0 , 0 -> 1
prev_point = polygons[best][endIdx];
incommingPerpundicularNormal = crossZ(normal(polygons[best][endIdx] - polygons[best][polyStart[best]], 1000));
picked[best_line_idx] = true;
polyOrder.push_back(best_line_idx);
picked[best] = true;
polyOrder.push_back(best);
}
else
{
logError("Failed to find next closest line.\n");
}
prev_point = startPoint;
for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon
{
int nr = polyOrder[n];
PolygonRef poly = polygons[nr];
int best = -1;
float bestDist = std::numeric_limits<float>::infinity();
bool orientation = poly.orientation();
for(unsigned int i=0;i<poly.size(); i++)
{
float dist = vSize2f(polygons[nr][i] - prev_point);
Point n0 = normal(poly[(i+poly.size()-1)%poly.size()] - poly[i], 2000);
Point n1 = normal(poly[i] - poly[(i + 1) % poly.size()], 2000);
float dot_score = dot(n0, n1) - dot(crossZ(n0), n1);
if (orientation)
dot_score = -dot_score;
if (dist + dot_score < bestDist)
{
best = i;
bestDist = dist + dot_score;
}
}
polyStart[nr] = best;
assert(poly.size() == 2);
prev_point = poly[best *-1 + 1]; /// 1 -> 0 , 0 -> 1
}
}
inline void LineOrderOptimizer::updateBestLine(unsigned int poly_idx, int& best, float& best_score, Point prev_point, Point incoming_perpundicular_normal)
inline void LineOrderOptimizer::checkIfLineIsBest(unsigned int i_line_polygon, int& best, float& bestDist, Point& prev_point, Point& incommingPerpundicularNormal)
{
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)
float dist = vSize2f(polygons[i_line_polygon][0] - prev_point);
dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i_line_polygon][1] - polygons[i_line_polygon][0], 1000))) * 0.0001f; /// penalize sharp corners
if (dist < bestDist)
{
best = poly_idx;
best_score = score;
polyStart[poly_idx] = 0;
best = i_line_polygon;
bestDist = dist;
polyStart[i_line_polygon] = 0;
}
}
{ /// check distance to second point on line (1)
float score = vSize2f(p1 - prev_point) + dot_score; // prefer 90 degree corners
if (score < best_score)
float dist = vSize2f(polygons[i_line_polygon][1] - prev_point);
dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i_line_polygon][0] - polygons[i_line_polygon][1], 1000) )) * 0.0001f; /// penalize sharp corners
if (dist < bestDist)
{
best = poly_idx;
best_score = score;
polyStart[poly_idx] = 1;
best = i_line_polygon;
bestDist = dist;
polyStart[i_line_polygon] = 1;
}
}
}
float LineOrderOptimizer::getAngleScore(Point incoming_perpundicular_normal, Point p0, Point p1)
{
return dot(incoming_perpundicular_normal, normal(p1 - p0, 1000)) * 0.0001f;
}
}//namespace cura
+10 -44
Ver Arquivo
@@ -4,29 +4,25 @@
#include <stdint.h>
#include "utils/polygon.h"
#include "settings.h"
namespace cura {
//! Parts order optimization class.
/*!
* Parts order optimization class.
*
* Utility class for optimizing the path order by minimizing the distance traveled between printing different parts in the layer.
* The order of polygons is optimized and the startingpoint within each polygon is chosen.
*/
* Utility class for optimizing the path order by minimizing the distance traveled between printing different parts in the layer.
* The order of polygons is optimized and the startingpoint within each polygon is chosen.
*/
class PathOrderOptimizer
{
public:
EZSeamType type;
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<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
PathOrderOptimizer(Point startPoint, EZSeamType type = EZSeamType::SHORTEST)
: type(type)
, startPoint(startPoint)
PathOrderOptimizer(Point startPoint)
{
this->startPoint = startPoint;
}
void addPolygon(PolygonRef polygon)
@@ -42,11 +38,8 @@ public:
void optimize(); //!< sets #polyStart and #polyOrder
private:
int getPolyStart(Point prev_point, int poly_idx);
int getClosestPointInPolygon(Point prev, int i_polygon); //!< returns the index of the closest point
int getFarthestPointInPolygon(int poly_idx); //!< return the index to the point farthest from the front (highest y)
int getRandomPointInPolygon(int poly_idx);
private:
int getClosestPointInPolygon(Point prev, int i_polygon); //!< returns the index of the closest point
};
@@ -81,35 +74,8 @@ public:
void optimize(); //!< sets #polyStart and #polyOrder
private:
/*!
* Update LineOrderOptimizer::polyStart if the current line is better than the current best.
*
* Besides looking at the distance from the previous line segment, we also look at the angle we make.
*
* 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
*/
void updateBestLine(unsigned int poly_idx, int& best, float& best_score, Point prev_point, Point incoming_perpundicular_normal);
void checkIfLineIsBest(unsigned int i_line_polygon, int& best, float& bestDist, Point& prev_point, Point& incommingPerpundicularNormal);
/*!
* 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
*
*/
static float getAngleScore(Point incoming_perpundicular_normal, Point from, Point to);
};
}//namespace cura
+54
Ver Arquivo
@@ -0,0 +1,54 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "polygonOptimizer.h"
namespace cura {
void optimizePolygon(PolygonRef poly)
{
Point p0 = poly[poly.size()-1];
for(unsigned int i=0;i<poly.size();i++)
{
Point p1 = poly[i];
if (shorterThen(p0 - p1, MICRON2INT(10)))
{
poly.remove(i);
i --;
}else if (shorterThen(p0 - p1, MICRON2INT(500)))
{
Point p2;
if (i < poly.size() - 1)
p2 = poly[i+1];
else
p2 = poly[0];
Point diff0 = normal(p1 - p0, 10000000);
Point diff2 = normal(p1 - p2, 10000000);
int64_t d = dot(diff0, diff2);
if (d < -99999999999999LL)
{
poly.remove(i);
i --;
}else{
p0 = p1;
}
}else{
p0 = p1;
}
}
}
void optimizePolygons(Polygons& polys)
{
for(unsigned int n=0;n<polys.size();n++)
{
optimizePolygon(polys[n]);
if (polys[n].size() < 3)
{
polys.remove(n);
n--;
}
}
}
}//namespace cura
+15
Ver Arquivo
@@ -0,0 +1,15 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#ifndef POLYGON_OPTIMIZER_H
#define POLYGON_OPTIMIZER_H
#include "utils/polygon.h"
namespace cura {
void optimizePolygon(PolygonRef poly);
void optimizePolygons(Polygons& polys);
}//namespace cura
#endif//POLYGON_OPTIMIZER_H
+11 -10
Ver Arquivo
@@ -6,18 +6,19 @@ namespace cura {
void generateRaft(SliceDataStorage& storage, int distance)
{
if (storage.draft_protection_shield.size() > 0)
for(SliceMeshStorage& mesh : storage.meshes)
{
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));
}
else
{
storage.raftOutline = storage.getLayerOutlines(0, true).offset(distance);
if (mesh.layers.size() < 1) continue;
SliceLayer* layer = &mesh.layers[0];
for(SliceLayerPart& part : layer->parts)
storage.raftOutline = storage.raftOutline.unionPolygons(part.outline.offset(distance));
}
Polygons support;
if (storage.support.generated)
support = storage.support.supportAreasPerLayer[0];
storage.raftOutline = storage.raftOutline.unionPolygons(support.offset(distance));
storage.raftOutline = storage.raftOutline.unionPolygons(storage.wipeTower.offset(distance));
}
}//namespace cura
+161 -316
Ver Arquivo
@@ -1,369 +1,214 @@
#include "settingRegistry.h"
#include <sstream>
#include <iostream> // debug IO
#include <libgen.h> // dirname
#include <string>
#include <algorithm> // find_if
#include "utils/logoutput.h"
#include "rapidjson/rapidjson.h"
#include "rapidjson/document.h"
#include "rapidjson/error/en.h"
#include "rapidjson/filereadstream.h"
#include "utils/logoutput.h"
namespace cura
{
SettingRegistry SettingRegistry::instance; // define settingRegistry
std::string SettingRegistry::toString(rapidjson::Type type)
{
switch (type)
{
case rapidjson::Type::kNullType: return "null";
case rapidjson::Type::kFalseType: return "false";
case rapidjson::Type::kTrueType: return "true";
case rapidjson::Type::kObjectType: return "object";
case rapidjson::Type::kArrayType: return "array";
case rapidjson::Type::kStringType: return "string";
case rapidjson::Type::kNumberType: return "number";
default: return "Unknown";
}
}
SettingContainer::SettingContainer(std::string key, std::string label)
: key(key)
, label(label)
{
}
SettingConfig* SettingContainer::addChild(std::string key, std::string label)
{
children.emplace_back(key, label);
return &children.back();
}
SettingConfig& SettingContainer::getOrCreateChild(std::string key, std::string label)
{
auto child_it = std::find_if(children.begin(), children.end(), [&key](SettingConfig& child) { return child.key == key; } );
if (child_it == children.end())
{
children.emplace_back(key, label);
return children.back();
}
else
{
return *child_it;
}
}
SettingConfig::SettingConfig(std::string key, std::string label)
: SettingContainer(key, label)
{
// std::cerr << key << std::endl; // debug output to show all frontend registered settings...
}
void SettingContainer::debugOutputAllSettings() const
{
std::cerr << "\nCATEGORY: " << key << std::endl;
for (const SettingConfig& child : children)
{
child.debugOutputAllSettings();
}
}
bool SettingRegistry::settingExists(std::string key) const
{
return settings.find(key) != settings.end();
}
SettingConfig* SettingRegistry::getSettingConfig(std::string key) const
const SettingConfig* SettingRegistry::getSettingConfig(std::string key)
{
auto it = settings.find(key);
if (it == settings.end())
if (settings.find(key) == settings.end())
return nullptr;
return it->second;
return settings[key];
}
SettingContainer* SettingRegistry::getCategory(std::string key)
{
for (SettingContainer& cat : categories)
if (cat.getKey().compare(key) == 0)
return &cat;
return nullptr;
}
const SettingContainer* SettingRegistry::getCategory(std::string key) const
{
for (const SettingContainer& cat : categories)
if (cat.getKey().compare(key) == 0)
return &cat;
return nullptr;
}
SettingContainer& SettingRegistry::getOrCreateCategory(std::string cat_name, const rapidjson::Value& category)
{
std::list<SettingContainer>::iterator category_found = std::find_if(categories.begin(), categories.end(), [&cat_name](SettingContainer& cat) { return cat.getKey().compare(cat_name) == 0; });
if (category_found != categories.end())
{ // category is already present; add settings to category
return *category_found;
}
else
{
std::string label = cat_name;
if (category.IsObject() && category.HasMember("label") && category["label"].IsString())
{
label = category["label"].GetString();
}
categories.emplace_back(cat_name, label);
return categories.back();
}
}
SettingRegistry::SettingRegistry()
{
}
bool SettingRegistry::settingsLoaded() const
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)
bool SettingRegistry::loadJSON(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);
FILE* f = fopen(filename.c_str(), "rb");
if (!f)
{
return false;
}
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(offset %u): %s\n", (unsigned)json_document.GetErrorOffset(), GetParseError_En(json_document.GetParseError()));
return 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;
return false;
}
if (json_document.HasMember("machine_extruder_trains"))
if (!json_document.HasMember("categories"))
{
const rapidjson::Value& trains = json_document["machine_extruder_trains"];
SettingContainer& category_trains = getOrCreateCategory("machine_extruder_trains", trains);
if (trains.IsObject())
{
for (rapidjson::Value::ConstMemberIterator train_iterator = trains.MemberBegin(); train_iterator != trains.MemberEnd(); ++train_iterator)
{
SettingConfig& child = category_trains.getOrCreateChild(train_iterator->name.GetString(), std::string("Extruder ") + train_iterator->name.GetString());
const rapidjson::Value& train = train_iterator->value;
for (rapidjson::Value::ConstMemberIterator setting_iterator = train.MemberBegin(); setting_iterator != train.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(&child, setting_iterator, warn_duplicates, false);
}
}
}
else if (trains.IsArray())
{
int train_nr = 0;
for (rapidjson::Value::ConstValueIterator train_iterator = trains.Begin(); train_iterator != trains.End(); ++train_iterator)
{
SettingConfig& child = category_trains.getOrCreateChild(std::to_string(train_nr), std::string("Extruder ") + std::to_string(train_nr));
const rapidjson::Value& train = *train_iterator;
for (rapidjson::Value::ConstMemberIterator setting_iterator = train.MemberBegin(); setting_iterator != train.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(&child, setting_iterator, warn_duplicates, false);
}
train_nr++;
}
}
}
if (json_document.HasMember("machine_settings"))
{
const rapidjson::Value& machine_settings = json_document["machine_settings"];
SettingContainer& category = getOrCreateCategory("machine_settings", machine_settings);
// _addCategory(std::string("machine_settings"), machine_settings, warn_duplicates); // TODO: make machine_settings a category with a settings field and a label field and throw away rest of the code in this code block
for (rapidjson::Value::ConstMemberIterator setting_iterator = machine_settings.MemberBegin(); setting_iterator != machine_settings.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(&category, setting_iterator, warn_duplicates);
}
return false;
}
if (json_document.HasMember("categories"))
{
for (rapidjson::Value::ConstMemberIterator category_iterator = json_document["categories"].MemberBegin(); category_iterator != json_document["categories"].MemberEnd(); ++category_iterator)
{
_addCategory(category_iterator->name.GetString(), category_iterator->value, warn_duplicates);
}
}
categories.emplace_back("machine_settings", "Machine Settings");
SettingCategory* category_machine_settings = &categories.back();
_addSettingsToCategory(category_machine_settings, json_document["machine_settings"], NULL);
if (json_document.HasMember("overrides"))
categories.emplace_back("mesh_settings", "TEMPORARY");
SettingCategory* category_mesh_settings = &categories.back();
{
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)
SettingConfig* config = category_mesh_settings->addChild("mesh_position_x", "mesh_position_x");
config->setDefault("0");
if (settingExists(config->getKey()))
{
std::string setting = override_iterator->name.GetString();
SettingConfig* conf = getSettingConfig(setting);
if (!conf) //Setting could not be found.
{
logWarning("Trying to override unknown setting %s.\n", setting.c_str());
continue;
}
_loadSettingValues(conf, override_iterator, false);
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
}
}
return 0;
}
void SettingRegistry::_addCategory(std::string cat_name, const rapidjson::Value& fields, bool warn_duplicates)
{
if (!fields.IsObject())
{
return;
}
if (!fields.HasMember("settings") || !fields["settings"].IsObject())
{
return;
}
SettingContainer& category = getOrCreateCategory(cat_name, fields);
const rapidjson::Value& json_object_container = fields["settings"];
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
{
_addSettingToContainer(&category, setting_iterator, warn_duplicates);
}
}
void SettingRegistry::_addSettingToContainer(SettingContainer* parent, const rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings)
{
const rapidjson::Value& data = json_object_it->value;
std::string label;
if (!json_object_it->value.HasMember("label") || !data["label"].IsString())
{
label = "N/A";
}
else
{
label = data["label"].GetString();
}
/// Create the new setting config object.
SettingConfig& config = parent->getOrCreateChild(json_object_it->name.GetString(), label);
_loadSettingValues(&config, json_object_it, warn_duplicates, add_to_settings);
}
void SettingRegistry::_loadSettingValues(SettingConfig* config, const rapidjson::GenericValue< rapidjson::UTF8< char > >::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings)
{
const rapidjson::Value& data = json_object_it->value;
/// Fill the setting config object with data we have in the json file.
if (data.HasMember("type") && data["type"].IsString())
{
config->setType(data["type"].GetString());
}
if (data.HasMember("default"))
{
const rapidjson::Value& dflt = data["default"];
if (dflt.IsString())
{
config->setDefault(dflt.GetString());
}
else if (dflt.IsTrue())
{
config->setDefault("true");
}
else if (dflt.IsFalse())
{
config->setDefault("false");
}
else if (dflt.IsNumber())
{
std::ostringstream ss;
ss << dflt.GetDouble();
config->setDefault(ss.str());
} // arrays are ignored because machine_extruder_trains needs to be handled separately
else
{
if (data.HasMember("type") && data["type"].IsString() &&
(data["type"].GetString() == std::string("polygon") || data["type"].GetString() == std::string("polygons")))
{
logWarning("WARNING: Loading polygon setting %s not implemented...\n", json_object_it->name.GetString());
}
else
{
logWarning("WARNING: Unrecognized data type in JSON: %s has type %s\n", json_object_it->name.GetString(), toString(dflt.GetType()).c_str());
}
}
}
if (data.HasMember("unit") && data["unit"].IsString())
{
config->setUnit(data["unit"].GetString());
}
/// Register the setting in the settings map lookup.
if (warn_duplicates && settingExists(config->getKey()))
{
cura::logError("Duplicate definition of setting: %s a.k.a. \"%s\" was already claimed by \"%s\"\n", config->getKey().c_str(), config->getLabel().c_str(), getSettingConfig(config->getKey())->getLabel().c_str());
}
if (add_to_settings)
{
settings[config->getKey()] = config;
}
/// When this setting has children, add those children to this setting.
if (data.HasMember("children") && data["children"].IsObject())
{
const rapidjson::Value& json_object_container = data["children"];
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
SettingConfig* config = category_mesh_settings->addChild("mesh_position_y", "mesh_position_y");
config->setDefault("0");
if (settingExists(config->getKey()))
{
_addSettingToContainer(config, setting_iterator, warn_duplicates, add_to_settings);
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
}
settings[config->getKey()] = config;
}
{
SettingConfig* config = category_mesh_settings->addChild("mesh_position_z", "mesh_position_z");
config->setDefault("0");
if (settingExists(config->getKey()))
{
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
}
settings[config->getKey()] = config;
}
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("label") || !category_iterator->value["label"].IsString())
{
continue;
}
if (!category_iterator->value.HasMember("settings") || !category_iterator->value["settings"].IsObject())
{
continue;
}
categories.emplace_back(category_iterator->name.GetString(), category_iterator->value["label"].GetString());
SettingCategory* category = &categories.back();
_addSettingsToCategory(category, category_iterator->value["settings"], NULL);
}
return true;
}
void SettingRegistry::_addSettingsToCategory(SettingCategory* category, const rapidjson::Value& json_object, SettingConfig* parent)
{
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object.MemberBegin(); setting_iterator != json_object.MemberEnd(); ++setting_iterator)
{
const rapidjson::Value& data = setting_iterator->value;
std::string label;
if (!setting_iterator->value.HasMember("label") || !data["label"].IsString())
{
label = "N/A";
}
else
{
label = data["label"].GetString();
}
/// Create the new setting config object.
SettingConfig* config;
if (parent)
config = parent->addChild(setting_iterator->name.GetString(), label);
else
config = category->addChild(setting_iterator->name.GetString(), label);
/// 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"))
{
if (data["default"].IsString())
{
config->setDefault(data["default"].GetString());
}
else if (data["default"].IsTrue())
{
config->setDefault("true");
}
else if (data["default"].IsFalse())
{
config->setDefault("false");
}
else if (data["default"].IsNumber())
{
std::ostringstream ss;
ss << data["default"].GetDouble();
config->setDefault(ss.str());
}
}
if (data.HasMember("unit") && data["unit"].IsString())
{
config->setUnit(data["unit"].GetString());
}
/// Register the setting in the settings map lookup.
if (settingExists(config->getKey()))
{
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
}
settings[config->getKey()] = config;
/// When this setting has children, add those children to this setting.
if (data.HasMember("children") && data["children"].IsObject())
{
_addSettingsToCategory(category, data["children"], config);
}
}
}
}//namespace cura
SettingCategory::SettingCategory(std::string key, std::string label)
: label(label), key(key)
{
}
SettingConfig* SettingCategory::addChild(std::string key, std::string label)
{
children.emplace_back(key, label, nullptr);
return &children.back();
}
SettingConfig::SettingConfig(std::string key, std::string label, SettingConfig* parent)
: label(label), key(key), parent(parent)
{
}
SettingConfig* SettingConfig::addChild(std::string key, std::string label)
{
children.emplace_back(key, label, this);
return &children.back();
}
+21 -170
Ver Arquivo
@@ -3,70 +3,28 @@
#include <vector>
#include <list>
#include <unordered_map>
#include <map>
#include <string>
#include <iostream> // debug out
#include "utils/NoCopy.h"
#include "rapidjson/document.h"
namespace cura
{
// Forward declaration
class SettingConfig;
class SettingRegistry;
/*!
* Setting category.
* Filled from the fdmprinter.json file. Contains one or more children settings.
*/
class SettingContainer
class SettingCategory
{
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::string key;
std::list<SettingConfig> children;
public:
std::string getKey() const { return key; }
std::string getLabel() const { return label; }
SettingContainer(std::string key, std::string label);
SettingCategory(std::string key, std::string label);
SettingConfig* addChild(std::string key, std::string label);
/*!
* Get the \p idx th child.
*
* This is used to get a specific extruder train in Settingsbase::setExtruderTrainDefaults
*
* \param idx The index in the list of children
* \return The \p idx th child
*/
const SettingConfig* getChild(unsigned int idx) const
{
if (idx < children.size())
{
auto it = children.begin();
while (idx > 0) { ++it; idx--; }
return &*it;
}
else
return nullptr;
}
private:
/*!
* Get the (direct) child with key \p key, or create one with key \p key and label \p label as well.
*
* \param key the key
* \param label the label for creating a new child
* \return The existing or newly created child setting.
*/
SettingConfig& getOrCreateChild(std::string key, std::string label);
public:
void debugOutputAllSettings() const;
};
/*!
@@ -74,23 +32,20 @@ public:
* 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
class SettingConfig
{
private:
std::string label;
std::string key;
std::string type;
std::string default_value;
std::string unit;
SettingConfig* parent;
std::list<SettingConfig> children;
public:
SettingConfig(std::string key, std::string label);
/*!
* Get the SettingConfig::children.
*
* This is used to get the extruder trains; see Settingsbase::setExtruderTrainDefaults
*
* \return SettingConfig::children
*/
const std::list<SettingConfig>& getChildren() const { return children; }
SettingConfig(std::string key, std::string label, SettingConfig* parent);
SettingConfig* addChild(std::string key, std::string label);
std::string getKey() const
{
@@ -126,15 +81,6 @@ public:
{
return unit;
}
void debugOutputAllSettings() const
{
std::cerr << key << "(" << default_value << ")" <<std::endl;
for (const SettingConfig& child : children)
{
child.debugOutputAllSettings();
}
}
};
/*!
@@ -142,121 +88,26 @@ public:
* There is a single global setting registry.
* This registry contains all known setting keys.
* The registry also contains the settings categories to build up the setting hiarcy from the json file.
* Also the default values are stored and retrieved in case a given setting doesn't get a value from the command line or the frontend.
*/
class SettingRegistry : NoCopy
class SettingRegistry
{
private:
static SettingRegistry instance;
SettingRegistry();
std::unordered_map<std::string, SettingConfig*> settings;
std::list<SettingContainer> categories;
std::map<std::string, SettingConfig*> settings;
std::list<SettingCategory> categories;
public:
/*!
* Get the SettingRegistry.
*
* This is a singleton class.
*
* \return The SettingRegistry
*/
static SettingRegistry* getInstance() { return &instance; }
bool settingExists(std::string key) const;
SettingConfig* getSettingConfig(std::string key) const;
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);
/*!
* Return the first category with the given key as name, or a null pointer. const style
*
* \param key the key as it is in the JSON file
* \return The first category in the list having the \p key
*/
const SettingContainer* getCategory(std::string key) const;
bool settingsLoaded();
bool loadJSON(std::string filename);
private:
/*!
* Return the first category with the given key as name, or a new one.
*
* \param cat_name the key as it is in the JSON file
* \param category the JSON Value associated with the key
* \return The first category in the list having the \p key (or a new one)
*/
SettingContainer& getOrCreateCategory(std::string cat_name, const rapidjson::Value& category);
public:
bool settingsLoaded() const;
/*!
* Load settings from a json file and all the parents it inherits from.
*
* Uses recursion to load the parent json file.
*
* \param filename The filename of the json file to parse
* \return an error code or zero of succeeded
*/
int loadJSONsettings(std::string filename);
void debugOutputAllSettings() const
{
for (const SettingContainer& cat : categories)
{
cat.debugOutputAllSettings();
}
}
SettingRegistry();
private:
/*!
* \param type type to convert to string
* \return human readable version of json type
*/
static std::string toString(rapidjson::Type type);
/*!
* Load a json document.
*
* \param filename The filename of the json file to parse
* \param json_document (output) the document to be loaded
* \return an error code or zero of succeeded
*/
int loadJSON(std::string filename, rapidjson::Document& json_document);
/*!
* Load settings from a single json file.
*
* \param filename The filename of the json file to parse
* \param warn_duplicates whether to warn for duplicate definitions
* \return an error code or zero of succeeded
*/
int loadJSONsettingsFromDoc(rapidjson::Document& json_document, bool warn_duplicates);
/*!
* Get the string from a json value (generally the default value field of a setting)
* \param dflt The value to convert to string
* \param setting_name The name of the setting (in case we need to display an error message)
* \return The string
*/
static std::string toString(const rapidjson::Value& dflt, std::string setting_name = "?");
/*!
* \param warn_duplicates whether to warn for duplicate definitions
*/
void _addSettingToContainer(SettingContainer* parent, const rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
/*!
* Adds a category with a given name to the registry.
* \param cat_name the key of the category as it is in the JSON file
* \param fields The members of the category
* \param warn_duplicates whether to warn for duplicate definitions
*/
void _addCategory(std::string cat_name, const rapidjson::Value& fields, bool warn_duplicates);
void _loadSettingValues(SettingConfig* config, const rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
void _addSettingsToCategory(SettingCategory* category, const rapidjson::Value& json_object, SettingConfig* parent);
};
}//namespace cura
#endif//SETTING_REGISTRY_H
+56 -226
Ver Arquivo
@@ -7,58 +7,21 @@
#include "settings.h"
#include "settingRegistry.h"
namespace cura
{
//c++11 no longer defines M_PI, so add our own constant.
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
std::string toString(EGCodeFlavor flavor)
{
switch (flavor)
{
case EGCodeFlavor::BFB:
return "BFB";
case EGCodeFlavor::MACH3:
return "Mach3";
case EGCodeFlavor::MAKERBOT:
return "Makerbot";
case EGCodeFlavor::ULTIGCODE:
return "UltiGCode";
case EGCodeFlavor::REPRAP_VOLUMATRIC:
return "RepRap(Volumetric)";
case EGCodeFlavor::REPRAP:
default:
return "RepRap";
}
}
SettingsBaseVirtual::SettingsBaseVirtual()
SettingsBase::SettingsBase()
: parent(NULL)
{
}
SettingsBaseVirtual::SettingsBaseVirtual(SettingsBaseVirtual* parent)
SettingsBase::SettingsBase(SettingsBase* parent)
: parent(parent)
{
}
SettingsBase::SettingsBase()
: SettingsBaseVirtual(NULL)
{
}
SettingsBase::SettingsBase(SettingsBaseVirtual* parent)
: SettingsBaseVirtual(parent)
{
}
SettingsMessenger::SettingsMessenger(SettingsBaseVirtual* parent)
: SettingsBaseVirtual(parent)
{
}
void SettingsBase::setSetting(std::string key, std::string value)
{
if (SettingRegistry::getInstance()->settingExists(key))
@@ -72,93 +35,69 @@ void SettingsBase::setSetting(std::string key, std::string value)
}
}
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);
}
SettingsBase& nonConstThis = const_cast<SettingsBase&>(*this);
if (SettingRegistry::getInstance()->settingExists(key))
{
nonConstThis.setting_values[key] = SettingRegistry::getInstance()->getSettingConfig(key)->getDefaultValue();
setting_values[key] = SettingRegistry::getInstance()->getSettingConfig(key)->getDefaultValue();
cura::logError("Using default for: %s = %s\n", key.c_str(), setting_values[key].c_str());
}
else
{
nonConstThis.setting_values[key] = "";
setting_values[key] = "";
cura::logError("Unregistered setting %s\n", key.c_str());
}
return setting_values.at(key);
return setting_values[key];
}
void SettingsMessenger::setSetting(std::string key, std::string value)
bool SettingsBase::hasSetting(std::string key)
{
parent->setSetting(key, value);
}
std::string SettingsMessenger::getSettingString(std::string key) const
{
return parent->getSettingString(key);
}
void SettingsBase::setExtruderTrainDefaults(unsigned int extruder_nr)
{
const SettingContainer* machine_extruder_trains = SettingRegistry::getInstance()->getCategory(std::string("machine_extruder_trains"));
if (!machine_extruder_trains)
if (setting_values.find(key) != setting_values.end())
{
// no machine_extruder_trains setting present; just use defaults for each train..
return;
return true;
}
const SettingConfig* train = machine_extruder_trains->getChild(extruder_nr);
if (!train)
if (parent)
{
// not enough machine_extruder_trains settings present; just use defaults for this train..
return;
return parent->hasSetting(key);
}
for (const SettingConfig& setting : train->getChildren())
{
if (setting_values.find(setting.getKey()) == setting_values.end())
{
setSetting(setting.getKey(), setting.getDefaultValue());
}
}
return false;
}
int SettingsBaseVirtual::getSettingAsIndex(std::string key) const
int SettingsBase::getSettingAsIndex(std::string key)
{
std::string value = getSettingString(key);
return atoi(value.c_str());
}
int SettingsBaseVirtual::getSettingAsCount(std::string key) const
int SettingsBase::getSettingAsCount(std::string key)
{
std::string value = getSettingString(key);
return atoi(value.c_str());
}
int SettingsBaseVirtual::getSettingInMicrons(std::string key) const
int SettingsBase::getSettingInMicrons(std::string key)
{
std::string value = getSettingString(key);
return atof(value.c_str()) * 1000.0;
}
double SettingsBaseVirtual::getSettingInAngleRadians(std::string key) const
double SettingsBase::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 SettingsBase::getSettingBoolean(std::string key)
{
std::string value = getSettingString(key);
if (value == "on")
@@ -167,192 +106,83 @@ bool SettingsBaseVirtual::getSettingBoolean(std::string key) const
return true;
if (value == "true" or value == "True") //Python uses "True"
return true;
int num = atoi(value.c_str());
return num != 0;
return atoi(value.c_str()) != 0;
}
double SettingsBaseVirtual::getSettingInDegreeCelsius(std::string key) const
double SettingsBase::getSettingInDegreeCelsius(std::string key)
{
std::string value = getSettingString(key);
return atof(value.c_str());
}
double SettingsBaseVirtual::getSettingInMillimetersPerSecond(std::string key) const
double SettingsBase::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 SettingsBase::getSettingInPercentage(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 SettingsBase::getSettingInSeconds(std::string key)
{
std::string value = getSettingString(key);
return std::max(0.0, atof(value.c_str()));
}
double SettingsBaseVirtual::getSettingInSeconds(std::string key) const
{
std::string value = getSettingString(key);
return std::max(0.0, atof(value.c_str()));
}
FlowTempGraph SettingsBaseVirtual::getSettingAsFlowTempGraph(std::string key) const
{
FlowTempGraph ret;
const char* c_str = getSettingString(key).c_str();
char const* char_p = c_str;
while (*char_p != '[')
{
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++;
}
char_p++; // skip the '['
for (; *char_p != '\0'; char_p++)
{
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;
}
}
return ret;
}
EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key) const
EGCodeFlavor SettingsBase::getSettingAsGCodeFlavor(std::string key)
{
std::string value = getSettingString(key);
if (value == "RepRap")
return EGCodeFlavor::REPRAP;
return GCODE_FLAVOR_REPRAP;
else if (value == "UltiGCode")
return EGCodeFlavor::ULTIGCODE;
return GCODE_FLAVOR_ULTIGCODE;
else if (value == "Makerbot")
return EGCodeFlavor::MAKERBOT;
return GCODE_FLAVOR_MAKERBOT;
else if (value == "BFB")
return EGCodeFlavor::BFB;
return GCODE_FLAVOR_BFB;
else if (value == "MACH3")
return EGCodeFlavor::MACH3;
return GCODE_FLAVOR_MACH3;
else if (value == "RepRap (Volumatric)")
return EGCodeFlavor::REPRAP_VOLUMATRIC;
return EGCodeFlavor::REPRAP;
return GCODE_FLAVOR_REPRAP_VOLUMATRIC;
return GCODE_FLAVOR_REPRAP;
}
EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key) const
EFillMethod SettingsBase::getSettingAsFillMethod(std::string key)
{
std::string value = getSettingString(key);
if (value == "lines")
return EFillMethod::LINES;
if (value == "grid")
return EFillMethod::GRID;
if (value == "triangles")
return EFillMethod::TRIANGLES;
if (value == "concentric")
return EFillMethod::CONCENTRIC;
if (value == "zigzag")
return EFillMethod::ZIG_ZAG;
return EFillMethod::NONE;
if (value == "Lines")
return Fill_Lines;
if (value == "Grid")
return Fill_Grid;
if (value == "Triangles")
return Fill_Triangles;
if (value == "Concentric")
return Fill_Concentric;
if (value == "ZigZag")
return Fill_ZigZag;
return Fill_None;
}
EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string key) const
EPlatformAdhesion SettingsBase::getSettingAsPlatformAdhesion(std::string key)
{
std::string value = getSettingString(key);
if (value == "brim")
return EPlatformAdhesion::BRIM;
if (value == "raft")
return EPlatformAdhesion::RAFT;
return EPlatformAdhesion::SKIRT;
if (value == "Brim")
return Adhesion_Brim;
if (value == "Raft")
return Adhesion_Raft;
return Adhesion_None;
}
ESupportType SettingsBaseVirtual::getSettingAsSupportType(std::string key) const
ESupportType SettingsBase::getSettingAsSupportType(std::string key)
{
std::string value = getSettingString(key);
if (value == "everywhere")
return ESupportType::EVERYWHERE;
if (value == "buildplate")
return ESupportType::PLATFORM_ONLY;
return ESupportType::NONE;
if (value == "Everywhere")
return Support_Everywhere;
if (value == "Touching Buildplate")
return Support_PlatformOnly;
return Support_None;
}
EZSeamType SettingsBaseVirtual::getSettingAsZSeamType(std::string key) const
{
std::string value = getSettingString(key);
if (value == "random")
return EZSeamType::RANDOM;
if (value == "shortest")
return EZSeamType::SHORTEST;
if (value == "back")
return EZSeamType::BACK;
return EZSeamType::SHORTEST;
}
ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
{
std::string value = getSettingString(key);
if (value == "normal")
return ESurfaceMode::NORMAL;
if (value == "surface")
return ESurfaceMode::SURFACE;
if (value == "both")
return ESurfaceMode::BOTH;
return ESurfaceMode::NORMAL;
}
FillPerimeterGapMode SettingsBaseVirtual::getSettingAsFillPerimeterGapMode(std::string key) const
{
std::string value = getSettingString(key);
if (value == "nowhere")
{
return FillPerimeterGapMode::NOWHERE;
}
if (value == "everywhere")
{
return FillPerimeterGapMode::EVERYWHERE;
}
if (value == "skin")
{
return FillPerimeterGapMode::SKIN;
}
return FillPerimeterGapMode::NOWHERE;
}
}//namespace cura
+46 -153
Ver Arquivo
@@ -3,16 +3,9 @@
#include <vector>
#include <map>
#include <unordered_map>
#include <sstream>
#include "utils/floatpoint.h"
#include "FlowTempGraph.h"
namespace cura
{
#ifndef VERSION
#define VERSION "DEV"
#endif
@@ -21,7 +14,7 @@ namespace cura
* Different flavors of GCode. Some machines require different types of GCode.
* The GCode flavor definition handles this as a big setting to make major or minor modifications to the GCode.
*/
enum class EGCodeFlavor
enum EGCodeFlavor
{
/**
* RepRap flavored GCode is Marlin/Sprinter/Repetier based GCode.
@@ -31,7 +24,7 @@ enum class EGCodeFlavor
* Retraction is done on E values with G1. Start/end code is added.
* M106 Sxxx and M107 are used to turn the fan on/off.
**/
REPRAP = 0,
GCODE_FLAVOR_REPRAP = 0,
/**
* UltiGCode flavored is Marlin based GCode.
* UltiGCode uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode.
@@ -41,7 +34,7 @@ enum class EGCodeFlavor
* Start/end code is not added.
* M106 Sxxx and M107 are used to turn the fan on/off.
**/
ULTIGCODE = 1,
GCODE_FLAVOR_ULTIGCODE = 1,
/**
* Makerbot flavored GCode.
* Looks a lot like RepRap GCode with a few changes. Requires MakerWare to convert to X3G files.
@@ -51,7 +44,7 @@ enum class EGCodeFlavor
* Fan OFF is M127 T0
* Homing is done with G162 X Y F2000
**/
MAKERBOT = 2,
GCODE_FLAVOR_MAKERBOT = 2,
/**
* Bits From Bytes GCode.
@@ -59,13 +52,13 @@ enum class EGCodeFlavor
* Need X,Y,Z,F on every line.
* Needs extruder ON/OFF (M101, M103), has auto-retrection (M227 S[2560*mm] P[2560*mm])
**/
BFB = 3,
GCODE_FLAVOR_BFB = 3,
/**
* MACH3 GCode
* MACH3 is CNC control software, which expects A/B/C/D for extruders, instead of E.
**/
MACH3 = 4,
GCODE_FLAVOR_MACH3 = 4,
/**
* RepRap volumatric flavored GCode is Marlin based GCode.
* Volumatric uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode.
@@ -74,123 +67,48 @@ enum class EGCodeFlavor
* Retraction is done with G10 and G11. Retraction settings are ignored. G10 S1 is used for multi-extruder switch retraction.
* M106 Sxxx and M107 are used to turn the fan on/off.
**/
REPRAP_VOLUMATRIC = 5,
GCODE_FLAVOR_REPRAP_VOLUMATRIC = 5,
};
/*!
* Converts a gcode flavor type to string so that it can be included in the gcode.
*/
std::string toString(EGCodeFlavor flavor);
/*!
* In Cura different infill methods are available.
* This enum defines which fill patterns are available to get a uniform naming troughout the engine.
* The different methods are used for top/bottom, support and sparse infill.
*/
enum class EFillMethod
enum EFillMethod
{
LINES,
GRID,
TRIANGLES,
CONCENTRIC,
ZIG_ZAG,
NONE
Fill_Lines,
Fill_Grid,
Fill_Triangles,
Fill_Concentric,
Fill_ZigZag,
Fill_None
};
/*!
* Type of platform adheasion
*/
enum class EPlatformAdhesion
enum EPlatformAdhesion
{
SKIRT,
BRIM,
RAFT
Adhesion_None,
Adhesion_Brim,
Adhesion_Raft
};
/*!
* Type of support material to generate
*/
enum class ESupportType
enum ESupportType
{
NONE,
PLATFORM_ONLY,
EVERYWHERE
};
enum class EZSeamType
{
RANDOM,
SHORTEST,
BACK
};
enum class ESurfaceMode
{
NORMAL,
SURFACE,
BOTH
};
enum class FillPerimeterGapMode
{
NOWHERE,
EVERYWHERE,
SKIN
Support_None,
Support_PlatformOnly,
Support_Everywhere
};
#define MAX_EXTRUDERS 16
//Maximum number of infill layers that can be combined into a single infill extrusion area.
#define MAX_INFILL_COMBINE 8
class SettingsBase;
/*!
* An abstract class for classes that can provide setting values.
* These are: SettingsBase, which contains setting information
* and SettingsMessenger, which can pass on setting information from a SettingsBase
*/
class SettingsBaseVirtual
{
protected:
SettingsBaseVirtual* parent;
public:
virtual std::string getSettingString(std::string key) const = 0;
virtual void setSetting(std::string key, std::string value) = 0;
virtual ~SettingsBaseVirtual() {}
SettingsBaseVirtual(); //!< SettingsBaseVirtual without a parent settings object
SettingsBaseVirtual(SettingsBaseVirtual* parent); //!< construct a SettingsBaseVirtual with a parent settings object
void setParent(SettingsBaseVirtual* parent) { this->parent = parent; }
SettingsBaseVirtual* getParent() { return parent; }
int getSettingAsIndex(std::string key) const;
int getSettingAsCount(std::string key) const;
double getSettingInAngleRadians(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;
FlowTempGraph getSettingAsFlowTempGraph(std::string key) const;
std::vector<std::pair<double, double>> getSettingAsPointVector(std::string key) const;
EGCodeFlavor getSettingAsGCodeFlavor(std::string key) const;
EFillMethod getSettingAsFillMethod(std::string key) const;
EPlatformAdhesion getSettingAsPlatformAdhesion(std::string key) const;
ESupportType getSettingAsSupportType(std::string key) const;
EZSeamType getSettingAsZSeamType(std::string key) const;
ESurfaceMode getSettingAsSurfaceMode(std::string key) const;
FillPerimeterGapMode getSettingAsFillPerimeterGapMode(std::string key) const;
};
//Maximum number of sparse layers that can be combined into a single sparse extrusion.
#define MAX_SPARSE_COMBINE 8
/*!
* Base class for every object that can hold settings.
@@ -199,61 +117,36 @@ public:
* Different conversion functions are available for settings to increase code clarity and in the future make
* unit conversions possible.
*/
class SettingsBase : public SettingsBaseVirtual
class SettingsBase
{
private:
std::unordered_map<std::string, std::string> setting_values;
std::map<std::string, std::string> setting_values;
SettingsBase* parent;
public:
SettingsBase(); //!< SettingsBase without a parent settings object
SettingsBase(SettingsBaseVirtual* parent); //!< construct a SettingsBase with a parent settings object
/*!
* Retrieve the defaults for each extruder train from the machine_extruder_trains settings
* and set the general settings to those defaults if they haven't been set yet.
*
* Only sets those settings which haven't already been set on that level - not looking at its parent (FffProcessor, meshgroup) or children (meshes).
*
* \param extruder_nr The index of which extruder train in machine_extruder_trains to get the settings from
*/
void setExtruderTrainDefaults(unsigned int extruder_nr);
SettingsBase();
SettingsBase(SettingsBase* parent);
bool hasSetting(std::string key);
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
{
std::stringstream sstream;
for (auto pair : setting_values)
{
if (!pair.second.empty())
{
sstream << " -s " << pair.first << "=\"" << pair.second << "\"";
}
}
return sstream.str();
}
std::string getSettingString(std::string key);
int getSettingAsIndex(std::string key);
int getSettingAsCount(std::string key);
void debugOutputAllLocalSettings() const
{
for (auto pair : setting_values)
std::cerr << pair.first << " : " << pair.second << std::endl;
}
};
/*!
* Base class for an object which passes on settings from another object.
* An object which is a subclass of SettingsMessenger can be handled as a SettingsBase;
* the difference is that such an object cannot hold any settings, but can only pass on the settings from its parent.
*/
class SettingsMessenger : public SettingsBaseVirtual
{
public:
SettingsMessenger(SettingsBaseVirtual* parent); //!< construct a SettingsMessenger with a parent settings object
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 getSettingInPercentage(std::string key);
double getSettingInSeconds(std::string key);
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)
EGCodeFlavor getSettingAsGCodeFlavor(std::string key);
EFillMethod getSettingAsFillMethod(std::string key);
EPlatformAdhesion getSettingAsPlatformAdhesion(std::string key);
ESupportType getSettingAsSupportType(std::string key);
};
}//namespace cura
#endif//SETTINGS_H
+59 -123
Ver Arquivo
@@ -1,107 +1,69 @@
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
#include "skin.h"
#include "polygonOptimizer.h"
#include "utils/polygonUtils.h"
#define MIN_AREA_SIZE (0.4 * 0.4)
namespace cura
{
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int insetCount, bool avoidOverlappingPerimeters)
{
generateSkinAreas(layerNr, storage, innermost_wall_extrusion_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
generateSkinAreas(layerNr, storage, extrusionWidth, downSkinCount, upSkinCount);
SliceLayer* layer = &storage.layers[layerNr];
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
{
SliceLayerPart* part = &layer->parts[partNr];
generateSkinInsets(part, extrusionWidth, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
generateSkinInsets(part, extrusionWidth, insetCount, avoidOverlappingPerimeters);
}
}
void generateSkinAreas(int layer_nr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic)
void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount)
{
SliceLayer& layer = storage.layers[layer_nr];
if (downSkinCount == 0 && upSkinCount == 0)
SliceLayer* layer = &storage.layers[layerNr];
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
{
return;
}
for(unsigned int partNr = 0; partNr < layer.parts.size(); partNr++)
{
SliceLayerPart& part = layer.parts[partNr];
SliceLayerPart* part = &layer->parts[partNr];
Polygons upskin = part->insets.back().offset(-extrusionWidth/2);
Polygons downskin = upskin;
if (int(part.insets.size()) < wall_line_count)
if (static_cast<int>(layerNr - downSkinCount) >= 0)
{
continue; // the last wall is not present, the part should only get inter preimeter gaps, but no skin.
}
Polygons upskin = part.insets.back().offset(-innermost_wall_extrusion_width/2);
Polygons downskin = (downSkinCount == 0)? Polygons() : upskin;
if (upSkinCount == 0) upskin = Polygons();
auto getInsidePolygons = [&part](SliceLayer& layer2)
SliceLayer* layer2 = &storage.layers[layerNr - downSkinCount];
for(SliceLayerPart& part2 : layer2->parts)
{
Polygons result;
for(SliceLayerPart& part2 : layer2.parts)
{
if (part.boundaryBox.hit(part2.boundaryBox))
result.add(part2.insets.back());
}
return result;
};
if (no_small_gaps_heuristic)
{
if (static_cast<int>(layer_nr - downSkinCount) >= 0)
{
downskin = downskin.difference(getInsidePolygons(storage.layers[layer_nr - downSkinCount])); // skin overlaps with the walls
}
if (static_cast<int>(layer_nr + upSkinCount) < static_cast<int>(storage.layers.size()))
{
upskin = upskin.difference(getInsidePolygons(storage.layers[layer_nr + upSkinCount])); // skin overlaps with the walls
if (part->boundaryBox.hit(part2.boundaryBox))
downskin = downskin.difference(part2.insets.back());
}
}
else
if (static_cast<int>(layerNr + upSkinCount) < static_cast<int>(storage.layers.size()))
{
if (layer_nr >= downSkinCount && downSkinCount > 0)
SliceLayer* layer2 = &storage.layers[layerNr + upSkinCount];
for(SliceLayerPart& part2 : layer2->parts)
{
Polygons not_air = getInsidePolygons(storage.layers[layer_nr - 1]);
for (int downskin_layer_nr = layer_nr - downSkinCount; downskin_layer_nr < layer_nr - 1; downskin_layer_nr++)
{
not_air = not_air.intersection(getInsidePolygons(storage.layers[downskin_layer_nr]));
}
downskin = downskin.difference(not_air); // skin overlaps with the walls
}
if (layer_nr < static_cast<int>(storage.layers.size()) - downSkinCount && upSkinCount > 0)
{
Polygons not_air = getInsidePolygons(storage.layers[layer_nr + 1]);
for (int upskin_layer_nr = layer_nr + 2; upskin_layer_nr < layer_nr + upSkinCount + 1; upskin_layer_nr++)
{
not_air = not_air.intersection(getInsidePolygons(storage.layers[upskin_layer_nr]));
}
upskin = upskin.difference(not_air); // skin overlaps with the walls
if (part->boundaryBox.hit(part2.boundaryBox))
upskin = upskin.difference(part2.insets.back());
}
}
Polygons skin = upskin.unionPolygons(downskin);
double minAreaSize = (2 * M_PI * INT2MM(extrusionWidth) * INT2MM(extrusionWidth)) * 0.3; // TODO: hardcoded value!
skin.removeSmallAreas(minAreaSize);
skin.removeSmallAreas(MIN_AREA_SIZE);
for (PolygonsPart& skin_area_part : skin.splitIntoParts())
for (Polygons& skin_area_part : skin.splitIntoParts())
{
part.skin_parts.emplace_back();
part.skin_parts.back().outline = skin_area_part;
part->skin_parts.emplace_back();
part->skin_parts.back().outline = skin_area_part;
}
}
}
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters)
{
if (insetCount == 0)
{
@@ -115,16 +77,15 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
skin_part.insets.push_back(Polygons());
if (i == 0)
{
PolygonUtils::offsetSafe(skin_part.outline, - extrusionWidth/2, extrusionWidth, skin_part.insets[0], avoidOverlappingPerimeters_0);
offsetSafe(skin_part.outline, - extrusionWidth/2, extrusionWidth, skin_part.insets[0], avoidOverlappingPerimeters);
Polygons in_between = skin_part.outline.difference(skin_part.insets[0].offset(extrusionWidth/2));
skin_part.perimeterGaps.add(in_between);
} else
{
PolygonUtils::offsetExtrusionWidth(skin_part.insets[i-1], true, extrusionWidth, skin_part.insets[i], &skin_part.perimeterGaps, avoidOverlappingPerimeters);
offsetExtrusionWidth(skin_part.insets[i-1], true, extrusionWidth, skin_part.insets[i], &skin_part.perimeterGaps, avoidOverlappingPerimeters);
}
// optimize polygons: remove unnnecesary verts
skin_part.insets[i].simplify();
optimizePolygons(skin_part.insets[i]);
if (skin_part.insets[i].size() < 1)
{
skin_part.insets.pop_back();
@@ -134,18 +95,13 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
}
}
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
void generateSparse(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap)
{
SliceLayer& layer = storage.layers[layerNr];
for(SliceLayerPart& part : layer.parts)
{
if (int(part.insets.size()) < wall_line_count)
{
part.infill_area.emplace_back(); // put empty polygon as (uncombined) infill
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);
Polygons sparse = part.insets.back().offset(-extrusionWidth / 2 - infill_skin_overlap);
for(SliceLayerPart& part2 : layer.parts)
{
@@ -153,62 +109,41 @@ void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_e
{
for(SkinPart& skin_part : part2.skin_parts)
{
infill = infill.difference(skin_part.outline);
sparse = sparse.difference(skin_part.outline);
}
}
}
infill.removeSmallAreas(MIN_AREA_SIZE);
sparse.removeSmallAreas(3.0);//(2 * M_PI * INT2MM(config.extrusionWidth) * INT2MM(config.extrusionWidth)) * 3;
part.infill_area.push_back(infill.offset(infill_skin_overlap));
part.sparse_outline.push_back(sparse.offset(infill_skin_overlap));
}
}
void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount)
void combineSparseLayers(int layerNr, SliceMeshStorage& storage, int amount)
{
if(amount <= 1) //If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid.
{
return;
}
if(storage.layers.empty() || storage.layers.size() - 1 < static_cast<size_t>(storage.getSettingAsCount("top_layers")) || storage.getSettingAsCount("infill_line_distance") <= 0) //No infill is even generated.
{
return;
}
/* We need to round down the layer index we start at to the nearest
divisible index. Otherwise we get some parts that have infill at divisible
layers and some at non-divisible layers. Those layers would then miss each
other. */
size_t min_layer = storage.getSettingAsCount("bottom_layers") + amount - 1;
min_layer -= min_layer % amount; //Round upwards to the nearest layer divisible by infill_sparse_combine.
size_t max_layer = storage.layers.size() - 1 - storage.getSettingAsCount("top_layers");
max_layer -= max_layer % amount; //Round downwards to the nearest layer divisible by infill_sparse_combine.
for(size_t layer_idx = min_layer;layer_idx <= max_layer;layer_idx += amount) //Skip every few layers, but extrude more.
{
SliceLayer* layer = &storage.layers[layer_idx];
SliceLayer* layer = &storage.layers[layerNr];
for(unsigned int n = 1;n < amount;n++)
for(int n=1; n<amount; n++)
{
if (layerNr < n)
break;
SliceLayer* layer2 = &storage.layers[layerNr - n];
for(SliceLayerPart& part : layer->parts)
{
if(layer_idx < n)
Polygons result;
for(SliceLayerPart& part2 : layer2->parts)
{
break;
}
SliceLayer* layer2 = &storage.layers[layer_idx - n];
for(SliceLayerPart& part : layer->parts)
{
Polygons result;
for(SliceLayerPart& part2 : layer2->parts)
if (part.boundaryBox.hit(part2.boundaryBox))
{
if(part.boundaryBox.hit(part2.boundaryBox))
{
Polygons intersection = part.infill_area[n - 1].intersection(part2.infill_area[0]).offset(-200).offset(200);
result.add(intersection);
part.infill_area[n - 1] = part.infill_area[n - 1].difference(intersection);
part2.infill_area[0] = part2.infill_area[0].difference(intersection);
}
Polygons intersection = part.sparse_outline[n - 1].intersection(part2.sparse_outline[0]).offset(-200).offset(200);
result.add(intersection);
part.sparse_outline[n - 1] = part.sparse_outline[n - 1].difference(intersection);
part2.sparse_outline[0] = part2.sparse_outline[0].difference(intersection);
}
part.infill_area.push_back(result);
}
part.sparse_outline.push_back(result);
}
}
}
@@ -241,7 +176,8 @@ void generatePerimeterGaps(int layer_nr, SliceMeshStorage& storage, int extrusio
}
part.perimeterGaps = part.perimeterGaps.intersection(outlines_above.xorPolygons(outlines_below));
}
part.perimeterGaps.removeSmallAreas(MIN_AREA_SIZE);
double minAreaSize = (2 * M_PI * INT2MM(extrusionWidth) * INT2MM(extrusionWidth)) * 0.3; // TODO: hardcoded value!
part.perimeterGaps.removeSmallAreas(minAreaSize);
}
}
+7 -31
Ver Arquivo
@@ -26,14 +26,10 @@ void generatePerimeterGaps(int layerNr, SliceMeshStorage& storage, int extrusion
* \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 innermost_wall_extrusion_width The line width of the inner most wall
* \param insetCount The number of perimeters to surround the skin
* \param no_small_gaps_heuristic A heuristic which assumes there will be no small gaps between bottom and top skin with a z size smaller than the skin size itself
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p storage)
*/
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int insetCount, bool avoidOverlappingPerimeters);
/*!
* Generate the skin areas (outlines)
@@ -43,10 +39,8 @@ void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, i
* \param extrusionWidth extrusionWidth
* \param downSkinCount The number of layers of bottom skin
* \param upSkinCount The number of layers of top skin
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
* \param no_small_gaps_heuristic A heuristic which assumes there will be no small gaps between bottom and top skin with a z size smaller than the skin size itself
*/
void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount);
/*!
* Generate the skin insets.
@@ -55,37 +49,19 @@ void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidt
* \param part The part where the skin outline information (input) is stored and where the skin insets (output) are stored.
* \param extrusionWidth extrusionWidth
* \param insetCount The number of perimeters to surround the skin
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p storage)
*/
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, 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.
*
* Generate Infill
* \param layerNr The index of the layer for which to generate the infill
* \param part The part where the insets (input) are stored and where the infill (output) is stored.
* \param innermost_wall_extrusion_width width of the innermost wall lines
* \param extrusionWidth width of the wall lines
* \param infill_skin_overlap overlap distance between infill and skin
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
*/
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
/*!
* \brief Combines the infill of multiple layers for a specified mesh.
*
* The infill layers are combined while the thickness of each layer is
* multiplied such that the infill should fill up again to the full height of
* all combined layers.
*
* \param storage The mesh to combine the infill layers of.
* \param amount The number of layers to combine.
*/
void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount);
void generateSparse(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap);
void combineSparseLayers(int layerNr, SliceMeshStorage& storage, int amount);
}//namespace cura
+65 -58
Ver Arquivo
@@ -2,87 +2,94 @@
#include "skirt.h"
#include "support.h"
#include <queue>
namespace cura
{
void generateSkirt(SliceDataStorage& storage, int distance, int count, int minLength)
void generateSkirt(SliceDataStorage& storage, int distance, int extrusionWidth, int count, int minLength)
{
if (count == 0) return;
bool externalOnly = (distance > 0); // whether to include holes or not
bool externalOnly = (distance > 0);
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 support;
if (storage.support.generated)
support = storage.support.supportAreasPerLayer[0];
{ // get support polygons
for(SliceMeshStorage& mesh : storage.meshes)
{
if (mesh.layers.size() < 1) continue;
SliceLayer* layer = &mesh.layers[0];
for(unsigned int i=0; i<layer->parts.size(); i++)
support = support.difference(layer->parts[i].outline);
}
// expand and contract to smooth the final polygon
if (count == 1 && distance > 0)
{
int dist = extrusionWidth * 5;
support = support.offset(dist).offset(-dist);
}
}
Polygons& skirt_primary_extruder = storage.skirt[primary_extruder];
int overshoot = 0; // distance by which to expand and contract the skirt to approximate the convex hull of the first layer
if (count == 1 && distance > 0)
{
overshoot = 100000; // 10 cm
}
bool get_convex_hull = count == 1 && distance > 0;
Polygons first_layer_outline = storage.getLayerOutlines(0, true, externalOnly);
std::vector<Polygons> skirts;
for(int skirtNr=0; skirtNr<count;skirtNr++)
{
int offsetDistance = distance + primary_extrusion_width * skirtNr + primary_extrusion_width / 2;
int offsetDistance = distance + extrusionWidth * skirtNr + extrusionWidth / 2 + overshoot;
skirts.emplace_back(first_layer_outline.offset(offsetDistance, ClipperLib::jtRound));
Polygons& skirt_polygons = skirts.back();
//Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area"
for(unsigned int n=0; n<skirt_polygons.size(); n++)
Polygons skirtPolygons(storage.wipeTower.offset(offsetDistance));
for(SliceMeshStorage& mesh : storage.meshes)
{
double area = skirt_polygons[n].area();
if (area < 0 && area > -primary_extrusion_width * primary_extrusion_width * 100)
skirt_polygons.remove(n--);
if (mesh.layers.size() < 1) continue;
SliceLayer* layer = &mesh.layers[0];
for(unsigned int i=0; i<layer->parts.size(); i++)
{
if (externalOnly)
{
Polygons p;
p.add(layer->parts[i].outline[0]);
skirtPolygons = skirtPolygons.unionPolygons(p.offset(offsetDistance, ClipperLib::jtRound));
}
else
{
skirtPolygons = skirtPolygons.unionPolygons(layer->parts[i].outline.offset(offsetDistance, ClipperLib::jtRound));
}
}
}
if (get_convex_hull)
skirtPolygons = skirtPolygons.unionPolygons(support.offset(offsetDistance, ClipperLib::jtRound));
//Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area"
for(unsigned int n=0; n<skirtPolygons.size(); n++)
{
skirt_polygons = skirt_polygons.convexHull();
}
double area = skirtPolygons[n].area();
if (area < 0 && area > -extrusionWidth * extrusionWidth * 100)
skirtPolygons.remove(n--);
}
skirt_primary_extruder.add(skirt_polygons);
storage.skirt.add(skirtPolygons);
int length = skirt_primary_extruder.polygonLength();
if (skirtNr + 1 >= count && length > 0 && length < minLength) // make brim have more lines when total length is too small
int lenght = storage.skirt.polygonLength();
if (skirtNr + 1 >= count && lenght > 0 && lenght < minLength) // make brim have more lines when total length is too small
count++;
}
if (false) // the code below is for the old prime tower
{ //Add a skirt UNDER the prime tower to make it stick better.
Polygons prime_tower = storage.primeTower.ground_poly.offset(-primary_extrusion_width / 2);
std::queue<Polygons> prime_tower_insets;
while(prime_tower.size() > 0)
{
prime_tower_insets.emplace(prime_tower);
prime_tower = prime_tower.offset(-primary_extrusion_width);
}
while (!prime_tower_insets.empty())
{
Polygons& inset = prime_tower_insets.back();
skirt_primary_extruder.add(inset);
prime_tower_insets.pop();
}
//Add a skirt under the wipetower to make it stick better.
Polygons wipe_tower = storage.wipeTower.offset(-extrusionWidth / 2);
while(wipe_tower.size() > 0)
{
storage.skirt.add(wipe_tower);
wipe_tower = wipe_tower.offset(-extrusionWidth);
}
{ // process other extruders' brim/skirt (as one brim line around the old brim)
int offset_distance = 0;
int last_width = primary_extrusion_width;
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
{
if (extruder == primary_extruder) { continue; }
int width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("skirt_line_width");
offset_distance += last_width / 2 + width/2;
last_width = width;
while (storage.skirt[extruder].polygonLength() < minLength)
{
storage.skirt[extruder].add(skirts.back().offset(offset_distance, ClipperLib::jtRound));
offset_distance += width;
}
}
if (overshoot > 0)
{
storage.skirt = storage.skirt.offset(-overshoot, ClipperLib::jtRound);
}
}

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