mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'master' into tm_rotcalipers
This commit is contained in:
		
						commit
						4a71c42f9b
					
				
					 90 changed files with 3806 additions and 3809 deletions
				
			
		
							
								
								
									
										2
									
								
								Build.PL
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Build.PL
									
										
									
									
									
								
							|  | @ -16,6 +16,8 @@ my %prereqs = qw( | |||
|     ExtUtils::MakeMaker             6.80 | ||||
|     ExtUtils::ParseXS               3.22 | ||||
|     ExtUtils::XSpp                  0 | ||||
|     ExtUtils::XSpp::Cmd             0 | ||||
|     ExtUtils::CppGuess              0 | ||||
|     ExtUtils::Typemaps              0 | ||||
|     ExtUtils::Typemaps::Basic       0 | ||||
|     File::Basename                  0 | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ if (MSVC) | |||
|     # /bigobj (Increase Number of Sections in .Obj file) | ||||
|     # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater | ||||
|     # Generate symbols at every build target, even for the release. | ||||
|     add_compile_options(-bigobj -Zm316 /Zi) | ||||
|     add_compile_options(-bigobj -Zm520 /Zi) | ||||
| endif () | ||||
| 
 | ||||
| # Display and check CMAKE_PREFIX_PATH | ||||
|  | @ -240,15 +240,34 @@ if(NOT WIN32) | |||
|     set(MINIMUM_BOOST_VERSION "1.64.0") | ||||
| endif() | ||||
| find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS system filesystem thread log locale regex) | ||||
| if(Boost_FOUND) | ||||
| #    include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) | ||||
|     if (APPLE) | ||||
|         # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 | ||||
|         add_definitions(-DBOOST_ASIO_DISABLE_KQUEUE) | ||||
|     endif() | ||||
|     if(NOT SLIC3R_STATIC) | ||||
|         add_definitions(-DBOOST_LOG_DYN_LINK) | ||||
|     endif() | ||||
| 
 | ||||
| add_library(boost_libs INTERFACE) | ||||
| add_library(boost_headeronly INTERFACE) | ||||
| 
 | ||||
| if (APPLE) | ||||
|     # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 | ||||
|     target_compile_definitions(boost_headeronly INTERFACE BOOST_ASIO_DISABLE_KQUEUE) | ||||
| endif() | ||||
| 
 | ||||
| if(NOT SLIC3R_STATIC) | ||||
|     target_compile_definitions(boost_headeronly INTERFACE BOOST_LOG_DYN_LINK) | ||||
| endif() | ||||
| 
 | ||||
| if(TARGET Boost::system) | ||||
|     message(STATUS "Boost::boost exists") | ||||
|     target_link_libraries(boost_headeronly INTERFACE Boost::boost) | ||||
|     target_link_libraries(boost_libs INTERFACE  | ||||
|         boost_headeronly # includes the custom compile definitions as well | ||||
|         Boost::system | ||||
|         Boost::filesystem | ||||
|         Boost::thread | ||||
|         Boost::log | ||||
|         Boost::locale | ||||
|         Boost::regex | ||||
|         ) | ||||
| else() | ||||
|     target_include_directories(boost_headeronly INTERFACE ${Boost_INCLUDE_DIRS}) | ||||
|     target_link_libraries(boost_libs INTERFACE boost_headeronly ${Boost_LIBRARIES}) | ||||
| endif() | ||||
| 
 | ||||
| # Find and configure intel-tbb | ||||
|  |  | |||
							
								
								
									
										2
									
								
								deps/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								deps/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -84,7 +84,7 @@ if (MSVC) | |||
|         dep_wxwidgets | ||||
|         dep_gtest | ||||
|         dep_nlopt | ||||
|         dep_qhull | ||||
|         # dep_qhull # Experimental | ||||
|         dep_zlib    # on Windows we still need zlib | ||||
|     ) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										27
									
								
								deps/deps-windows.cmake
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								deps/deps-windows.cmake
									
										
									
									
										vendored
									
									
								
							|  | @ -1,21 +1,34 @@ | |||
| 
 | ||||
| # https://cmake.org/cmake/help/latest/variable/MSVC_VERSION.html | ||||
| if (MSVC_VERSION EQUAL 1800) | ||||
| # 1800      = VS 12.0 (v120 toolset) | ||||
|     set(DEP_VS_VER "12") | ||||
|     set(DEP_BOOST_TOOLSET "msvc-12.0") | ||||
| elseif (MSVC_VERSION EQUAL 1900) | ||||
| # 1900      = VS 14.0 (v140 toolset)     | ||||
|     set(DEP_VS_VER "14") | ||||
|     set(DEP_BOOST_TOOLSET "msvc-14.0") | ||||
| elseif (MSVC_VERSION GREATER 1900) | ||||
| elseif (MSVC_VERSION LESS 1920) | ||||
| # 1910-1919 = VS 15.0 (v141 toolset) | ||||
|     set(DEP_VS_VER "15") | ||||
|     set(DEP_BOOST_TOOLSET "msvc-14.1") | ||||
| elseif (MSVC_VERSION LESS 1930) | ||||
| # 1920-1929 = VS 16.0 (v142 toolset) | ||||
|     set(DEP_VS_VER "16") | ||||
|     set(DEP_BOOST_TOOLSET "msvc-14.2") | ||||
| else () | ||||
|     message(FATAL_ERROR "Unsupported MSVC version") | ||||
| endif () | ||||
| 
 | ||||
| if (${DEPS_BITS} EQUAL 32) | ||||
|     set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") | ||||
|     set(DEP_PLATFORM "Win32") | ||||
| else () | ||||
|     set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER} Win64") | ||||
|     if (DEP_VS_VER LESS 16) | ||||
|         set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER} Win64") | ||||
|     else () | ||||
|         set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") | ||||
|     endif () | ||||
|     set(DEP_PLATFORM "x64") | ||||
| endif () | ||||
| 
 | ||||
| 
 | ||||
|  | @ -28,8 +41,8 @@ endif () | |||
| 
 | ||||
| ExternalProject_Add(dep_boost | ||||
|     EXCLUDE_FROM_ALL 1 | ||||
|     URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" | ||||
|     URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 | ||||
|     URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" | ||||
|     URL_HASH SHA256=882b48708d211a5f48e60b0124cf5863c1534cd544ecd0664bb534a4b5d506e9 | ||||
|     BUILD_IN_SOURCE 1 | ||||
|     CONFIGURE_COMMAND bootstrap.bat | ||||
|     BUILD_COMMAND b2.exe | ||||
|  | @ -57,6 +70,7 @@ ExternalProject_Add(dep_tbb | |||
|     URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" | ||||
|     URL_HASH SHA256=0545cb6033bd1873fcae3ea304def720a380a88292726943ae3b9b207f322efe | ||||
|     CMAKE_GENERATOR "${DEP_MSVC_GEN}" | ||||
|     CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" | ||||
|     CMAKE_ARGS | ||||
|         -DCMAKE_DEBUG_POSTFIX=_debug | ||||
|         -DTBB_BUILD_SHARED=OFF | ||||
|  | @ -81,6 +95,7 @@ ExternalProject_Add(dep_gtest | |||
|     URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" | ||||
|     URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c | ||||
|     CMAKE_GENERATOR "${DEP_MSVC_GEN}" | ||||
|     CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" | ||||
|     CMAKE_ARGS | ||||
|         -DBUILD_GMOCK=OFF | ||||
|         -Dgtest_force_shared_crt=ON | ||||
|  | @ -105,6 +120,7 @@ ExternalProject_Add(dep_nlopt | |||
|     URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" | ||||
|     URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae | ||||
|     CMAKE_GENERATOR "${DEP_MSVC_GEN}" | ||||
|     CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" | ||||
|     CMAKE_ARGS | ||||
|         -DBUILD_SHARED_LIBS=OFF | ||||
|         -DNLOPT_PYTHON=OFF | ||||
|  | @ -133,6 +149,7 @@ ExternalProject_Add(dep_zlib | |||
|     URL "https://zlib.net/zlib-1.2.11.tar.xz" | ||||
|     URL_HASH SHA256=4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066 | ||||
|     CMAKE_GENERATOR "${DEP_MSVC_GEN}" | ||||
|     CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" | ||||
|     CMAKE_ARGS | ||||
|         -DSKIP_INSTALL_FILES=ON                                    # Prevent installation of man pages et al. | ||||
|         "-DINSTALL_BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}\\fallout"   # I found no better way of preventing zlib from creating & installing DLLs :-/ | ||||
|  |  | |||
							
								
								
									
										36
									
								
								deps/qhull-mods.patch
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								deps/qhull-mods.patch
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,16 @@ | |||
| From a31ae4781a4afa60e21c70e5b4ae784bcd447c8a Mon Sep 17 00:00:00 2001 | ||||
| From: tamasmeszaros <meszaros.q@gmail.com> | ||||
| Date: Thu, 6 Jun 2019 15:41:43 +0200 | ||||
| Subject: [PATCH] prusa-slicer changes | ||||
| 
 | ||||
| ---
 | ||||
|  CMakeLists.txt                     | 44 +++++++++++++++++++++++++++++++++++--- | ||||
|  Config.cmake.in                    |  2 ++ | ||||
|  src/libqhull_r/qhull_r-exports.def |  2 ++ | ||||
|  src/libqhull_r/user_r.h            |  2 +- | ||||
|  4 files changed, 46 insertions(+), 4 deletions(-) | ||||
|  create mode 100644 Config.cmake.in | ||||
| 
 | ||||
| diff --git a/CMakeLists.txt b/CMakeLists.txt
 | ||||
| index 59dff41..20c2ec5 100644
 | ||||
| --- a/CMakeLists.txt
 | ||||
|  | @ -70,6 +83,26 @@ index 0000000..bc92bfe | |||
| @@ -0,0 +1,2 @@
 | ||||
| +include("${CMAKE_CURRENT_LIST_DIR}/QhullTargets.cmake")
 | ||||
| +
 | ||||
| diff --git a/src/libqhull_r/qhull_r-exports.def b/src/libqhull_r/qhull_r-exports.def
 | ||||
| index 325d57c..72f6ad0 100644
 | ||||
| --- a/src/libqhull_r/qhull_r-exports.def
 | ||||
| +++ b/src/libqhull_r/qhull_r-exports.def
 | ||||
| @@ -185,6 +185,7 @@ qh_memsetup
 | ||||
|  qh_memsize | ||||
|  qh_memstatistics | ||||
|  qh_memtotal | ||||
| +qh_memcheck
 | ||||
|  qh_merge_degenredundant | ||||
|  qh_merge_nonconvex | ||||
|  qh_mergecycle | ||||
| @@ -372,6 +373,7 @@ qh_settruncate
 | ||||
|  qh_setunique | ||||
|  qh_setvoronoi_all | ||||
|  qh_setzero | ||||
| +qh_setendpointer
 | ||||
|  qh_sharpnewfacets | ||||
|  qh_skipfacet | ||||
|  qh_skipfilename | ||||
| diff --git a/src/libqhull_r/user_r.h b/src/libqhull_r/user_r.h
 | ||||
| index fc105b9..7cca65a 100644
 | ||||
| --- a/src/libqhull_r/user_r.h
 | ||||
|  | @ -83,3 +116,6 @@ index fc105b9..7cca65a 100644 | |||
|   | ||||
|  #if (REALfloat == 1) | ||||
|  #define realT float | ||||
| -- 
 | ||||
| 2.16.2.windows.1 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								resources/icons/mirroring_off.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/mirroring_off.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 589 B | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/mirroring_on.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/mirroring_on.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 600 B | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/mirroring_transparent.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/mirroring_transparent.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 93 B | 
|  | @ -15,6 +15,7 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); | |||
| #define INTENSITY_AMBIENT    0.3 | ||||
| 
 | ||||
| uniform mat4 volume_world_matrix; | ||||
| uniform float object_max_z; | ||||
| 
 | ||||
| // x = tainted, y = specular; | ||||
| varying vec2 intensity; | ||||
|  | @ -42,6 +43,12 @@ void main() | |||
|     intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; | ||||
| 
 | ||||
|     // Scaled to widths of the Z texture. | ||||
|     object_z = (volume_world_matrix * gl_Vertex).z; | ||||
|     if (object_max_z > 0.0) | ||||
|         // when rendering the overlay | ||||
|         object_z = object_max_z * gl_MultiTexCoord0.y; | ||||
|     else | ||||
|         // when rendering the volumes | ||||
|         object_z = (volume_world_matrix * gl_Vertex).z; | ||||
|          | ||||
|     gl_Position = ftransform(); | ||||
| } | ||||
|  |  | |||
|  | @ -139,7 +139,7 @@ if (MSVC) | |||
|     target_compile_definitions(PrusaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE) | ||||
|     add_dependencies(PrusaSlicer_app_gui PrusaSlicer) | ||||
|     set_target_properties(PrusaSlicer_app_gui PROPERTIES OUTPUT_NAME "prusa-slicer") | ||||
|     target_include_directories(PrusaSlicer_app_gui SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) | ||||
|     target_link_libraries(PrusaSlicer_app_gui PRIVATE boost_headeronly) | ||||
| 
 | ||||
|     add_executable(PrusaSlicer_app_console PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc) | ||||
|     # Generate debug symbols even in release mode. | ||||
|  | @ -147,7 +147,7 @@ if (MSVC) | |||
|     target_compile_definitions(PrusaSlicer_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE) | ||||
|     add_dependencies(PrusaSlicer_app_console PrusaSlicer) | ||||
|     set_target_properties(PrusaSlicer_app_console PROPERTIES OUTPUT_NAME "prusa-slicer-console") | ||||
|     target_include_directories(PrusaSlicer_app_console SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) | ||||
|     target_link_libraries(PrusaSlicer_app_console PRIVATE boost_headeronly) | ||||
| endif () | ||||
| 
 | ||||
| # Link the resources dir to where Slic3r GUI expects it | ||||
|  |  | |||
|  | @ -7,10 +7,13 @@ | |||
|     #include <Windows.h> | ||||
|     #include <wchar.h> | ||||
|     #ifdef SLIC3R_GUI | ||||
|     extern "C"  | ||||
|     {  | ||||
|         // Let the NVIDIA and AMD know we want to use their graphics card
 | ||||
|         // on a dual graphics card system.
 | ||||
|         __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; | ||||
|         __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; | ||||
|     } | ||||
|     #endif /* SLIC3R_GUI */ | ||||
| #endif /* WIN32 */ | ||||
| 
 | ||||
|  | @ -241,8 +244,7 @@ int CLI::run(int argc, char **argv) | |||
|         } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { | ||||
|             std::vector<Model> new_models; | ||||
|             for (auto &model : m_models) { | ||||
|                 model.repair(); | ||||
|                 model.translate(0, 0, -model.bounding_box().min.z());  // align to z = 0                
 | ||||
|                 model.translate(0, 0, -model.bounding_box().min.z());  // align to z = 0
 | ||||
| 				size_t num_objects = model.objects.size(); | ||||
| 				for (size_t i = 0; i < num_objects; ++ i) { | ||||
| 
 | ||||
|  | @ -301,8 +303,9 @@ int CLI::run(int argc, char **argv) | |||
|                 } | ||||
|             } | ||||
|         } else if (opt_key == "repair") { | ||||
|             for (auto &model : m_models) | ||||
|                 model.repair(); | ||||
| 			// Models are repaired by default.
 | ||||
|             //for (auto &model : m_models)
 | ||||
|             //    model.repair();
 | ||||
|         } else { | ||||
|             boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; | ||||
|             return 1; | ||||
|  |  | |||
|  | @ -8,10 +8,13 @@ | |||
| #include <wchar.h> | ||||
| 
 | ||||
| #ifdef SLIC3R_GUI | ||||
| extern "C"  | ||||
| {  | ||||
| 	// Let the NVIDIA and AMD know we want to use their graphics card
 | ||||
| 	// on a dual graphics card system.
 | ||||
| 	__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; | ||||
| 	__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; | ||||
| } | ||||
| #endif /* SLIC3R_GUI */ | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
|  |  | |||
|  | @ -11,4 +11,4 @@ add_library(admesh STATIC | |||
|     util.cpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(admesh SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) | ||||
| target_link_libraries(admesh PRIVATE boost_headeronly) | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -25,271 +25,214 @@ | |||
| #include <string.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
| // Boost pool: Don't use mutexes to synchronize memory allocation.
 | ||||
| #define BOOST_POOL_NO_MT | ||||
| #include <boost/pool/object_pool.hpp> | ||||
| 
 | ||||
| #include "stl.h" | ||||
| 
 | ||||
| static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); | ||||
| static void reverse_facet(stl_file *stl, int facet_num) | ||||
| { | ||||
| 	++ stl->stats.facets_reversed; | ||||
| 
 | ||||
| static void | ||||
| stl_reverse_facet(stl_file *stl, int facet_num) { | ||||
|   stl_vertex tmp_vertex; | ||||
|   /*  int tmp_neighbor;*/ | ||||
|   int neighbor[3]; | ||||
|   int vnot[3]; | ||||
| 	int neighbor[3] = { stl->neighbors_start[facet_num].neighbor[0], stl->neighbors_start[facet_num].neighbor[1], stl->neighbors_start[facet_num].neighbor[2] }; | ||||
| 	int vnot[3] = { stl->neighbors_start[facet_num].which_vertex_not[0], stl->neighbors_start[facet_num].which_vertex_not[1], stl->neighbors_start[facet_num].which_vertex_not[2] }; | ||||
| 
 | ||||
|   stl->stats.facets_reversed += 1; | ||||
| 	// reverse the facet
 | ||||
| 	stl_vertex tmp_vertex = stl->facet_start[facet_num].vertex[0]; | ||||
| 	stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1]; | ||||
| 	stl->facet_start[facet_num].vertex[1] = tmp_vertex; | ||||
| 
 | ||||
|   neighbor[0] = stl->neighbors_start[facet_num].neighbor[0]; | ||||
|   neighbor[1] = stl->neighbors_start[facet_num].neighbor[1]; | ||||
|   neighbor[2] = stl->neighbors_start[facet_num].neighbor[2]; | ||||
|   vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0]; | ||||
|   vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1]; | ||||
|   vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2]; | ||||
| 	// fix the vnots of the neighboring facets
 | ||||
| 	if (neighbor[0] != -1) | ||||
| 		stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = (stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; | ||||
| 	if (neighbor[1] != -1) | ||||
| 		stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; | ||||
| 	if (neighbor[2] != -1) | ||||
| 		stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; | ||||
| 
 | ||||
|   /* reverse the facet */ | ||||
|   tmp_vertex = stl->facet_start[facet_num].vertex[0]; | ||||
|   stl->facet_start[facet_num].vertex[0] = | ||||
|     stl->facet_start[facet_num].vertex[1]; | ||||
|   stl->facet_start[facet_num].vertex[1] = tmp_vertex; | ||||
| 	// swap the neighbors of the facet that is being reversed
 | ||||
| 	stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; | ||||
| 	stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; | ||||
| 
 | ||||
|   /* fix the vnots of the neighboring facets */ | ||||
|   if(neighbor[0] != -1) | ||||
|     stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = | ||||
|       (stl->neighbors_start[neighbor[0]]. | ||||
|        which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; | ||||
|   if(neighbor[1] != -1) | ||||
|     stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = | ||||
|       (stl->neighbors_start[neighbor[1]]. | ||||
|        which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; | ||||
|   if(neighbor[2] != -1) | ||||
|     stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = | ||||
|       (stl->neighbors_start[neighbor[2]]. | ||||
|        which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; | ||||
| 	// swap the vnots of the facet that is being reversed 
 | ||||
| 	stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; | ||||
| 	stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; | ||||
| 
 | ||||
|   /* swap the neighbors of the facet that is being reversed */ | ||||
|   stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; | ||||
|   stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; | ||||
| 
 | ||||
|   /* swap the vnots of the facet that is being reversed */ | ||||
|   stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; | ||||
|   stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; | ||||
| 
 | ||||
|   /* reverse the values of the vnots of the facet that is being reversed */ | ||||
|   stl->neighbors_start[facet_num].which_vertex_not[0] = | ||||
|     (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; | ||||
|   stl->neighbors_start[facet_num].which_vertex_not[1] = | ||||
|     (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; | ||||
|   stl->neighbors_start[facet_num].which_vertex_not[2] = | ||||
|     (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; | ||||
| 	// reverse the values of the vnots of the facet that is being reversed
 | ||||
| 	stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; | ||||
| 	stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; | ||||
| 	stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_fix_normal_directions(stl_file *stl) { | ||||
|   char *norm_sw; | ||||
|   /*  int edge_num;*/ | ||||
|   /*  int vnot;*/ | ||||
|   int checked = 0; | ||||
|   int facet_num; | ||||
|   /*  int next_facet;*/ | ||||
|   int i; | ||||
|   int j; | ||||
|   struct stl_normal { | ||||
|     int               facet_num; | ||||
|     struct stl_normal *next; | ||||
|   }; | ||||
|   struct stl_normal *head; | ||||
|   struct stl_normal *tail; | ||||
|   struct stl_normal *newn; | ||||
|   struct stl_normal *temp; | ||||
| // Returns true if the normal was flipped.
 | ||||
| static bool check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) | ||||
| { | ||||
| 	stl_facet *facet = &stl->facet_start[facet_num]; | ||||
| 
 | ||||
|   int* reversed_ids; | ||||
|   int reversed_count = 0; | ||||
|   int id; | ||||
|   int force_exit = 0; | ||||
| 	stl_normal normal; | ||||
| 	stl_calculate_normal(normal, facet); | ||||
| 	stl_normalize_vector(normal); | ||||
| 	stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 	const float eps = 0.001f; | ||||
| 	if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { | ||||
| 		// Normal is within tolerance. It is not really necessary to change the values here, but just for consistency, I will.
 | ||||
| 		facet->normal = normal; | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
|   // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
 | ||||
|   if (stl->stats.number_of_facets == 0) return; | ||||
| 	stl_normal test_norm = facet->normal; | ||||
| 	stl_normalize_vector(test_norm); | ||||
| 	normal_dif = (normal - test_norm).cwiseAbs(); | ||||
| 	if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { | ||||
| 		// The normal is not within tolerance, but direction is OK.
 | ||||
| 		if (normal_fix_flag) { | ||||
| 	  		facet->normal = normal; | ||||
| 	  		++ stl->stats.normals_fixed; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
|   /* Initialize linked list. */ | ||||
|   head = (struct stl_normal*)malloc(sizeof(struct stl_normal)); | ||||
|   if(head == NULL) perror("stl_fix_normal_directions"); | ||||
|   tail = (struct stl_normal*)malloc(sizeof(struct stl_normal)); | ||||
|   if(tail == NULL) perror("stl_fix_normal_directions"); | ||||
|   head->next = tail; | ||||
|   tail->next = tail; | ||||
| 
 | ||||
|   /* Initialize list that keeps track of already fixed facets. */ | ||||
|   norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); | ||||
|   if(norm_sw == NULL) perror("stl_fix_normal_directions"); | ||||
| 
 | ||||
|   /* Initialize list that keeps track of reversed facets. */ | ||||
|   reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int)); | ||||
|   if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids"); | ||||
| 
 | ||||
|   facet_num = 0; | ||||
|   /* If normal vector is not within tolerance and backwards:
 | ||||
|      Arbitrarily starts at face 0.  If this one is wrong, we're screwed.  Thankfully, the chances | ||||
|      of it being wrong randomly are low if most of the triangles are right: */ | ||||
|   if (stl_check_normal_vector(stl, 0, 0) == 2) { | ||||
|       stl_reverse_facet(stl, 0); | ||||
|       reversed_ids[reversed_count++] = 0; | ||||
|   } | ||||
| 
 | ||||
|   /* Say that we've fixed this facet: */ | ||||
|   norm_sw[facet_num] = 1; | ||||
|   checked++; | ||||
| 
 | ||||
|   for(;;) { | ||||
|     /* Add neighbors_to_list.
 | ||||
|        Add unconnected neighbors to the list:a  */ | ||||
|     for(j = 0; j < 3; j++) { | ||||
|       /* Reverse the neighboring facets if necessary. */ | ||||
|       if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { | ||||
|         /* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */ | ||||
|         if(stl->neighbors_start[facet_num].neighbor[j] != -1) { | ||||
|             if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { | ||||
|                 /* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */ | ||||
|                 for (id = reversed_count - 1; id >= 0; --id) { | ||||
|                     stl_reverse_facet(stl, reversed_ids[id]); | ||||
|                 } | ||||
|                 force_exit = 1; | ||||
|                 break; | ||||
|             } else { | ||||
|                 stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); | ||||
|                 reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j]; | ||||
|             } | ||||
|         } | ||||
|       } | ||||
|       /* If this edge of the facet is connected: */ | ||||
|       if(stl->neighbors_start[facet_num].neighbor[j] != -1) { | ||||
|         /* If we haven't fixed this facet yet, add it to the list: */ | ||||
|         if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { | ||||
|           /* Add node to beginning of list. */ | ||||
|           newn = (struct stl_normal*)malloc(sizeof(struct stl_normal)); | ||||
|           if(newn == NULL) perror("stl_fix_normal_directions"); | ||||
|           newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; | ||||
|           newn->next = head->next; | ||||
|           head->next = newn; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     /* an error occourred, quit the for loop and exit */ | ||||
|     if (force_exit) break; | ||||
| 
 | ||||
|     /* Get next facet to fix from top of list. */ | ||||
|     if(head->next != tail) { | ||||
|       facet_num = head->next->facet_num; | ||||
|       if(norm_sw[facet_num] != 1) { /* If facet is in list mutiple times */ | ||||
|         norm_sw[facet_num] = 1; /* Record this one as being fixed. */ | ||||
|         checked++; | ||||
|       } | ||||
|       temp = head->next;	/* Delete this facet from the list. */ | ||||
|       head->next = head->next->next; | ||||
|       free(temp); | ||||
|     } else { /* if we ran out of facets to fix: */ | ||||
|       /* All of the facets in this part have been fixed. */ | ||||
|       stl->stats.number_of_parts += 1; | ||||
|       if(checked >= stl->stats.number_of_facets) { | ||||
|         /* All of the facets have been checked.  Bail out. */ | ||||
|         break; | ||||
|       } else { | ||||
|         /* There is another part here.  Find it and continue. */ | ||||
|         for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|           if(norm_sw[i] == 0) { | ||||
|             /* This is the first facet of the next part. */ | ||||
|             facet_num = i; | ||||
|             if(stl_check_normal_vector(stl, i, 0) == 2) { | ||||
|                 stl_reverse_facet(stl, i); | ||||
|                 reversed_ids[reversed_count++] = i; | ||||
|             } | ||||
| 
 | ||||
|             norm_sw[facet_num] = 1; | ||||
|             checked++; | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   free(head); | ||||
|   free(tail); | ||||
|   free(reversed_ids); | ||||
|   free(norm_sw); | ||||
| 	test_norm *= -1.f; | ||||
| 	normal_dif = (normal - test_norm).cwiseAbs(); | ||||
| 	if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { | ||||
| 		// The normal is not within tolerance and backwards.
 | ||||
| 		if (normal_fix_flag) { | ||||
| 	  		facet->normal = normal; | ||||
| 	  		++ stl->stats.normals_fixed; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 	if (normal_fix_flag) { | ||||
| 		facet->normal = normal; | ||||
| 		++ stl->stats.normals_fixed; | ||||
| 	} | ||||
| 	// Status is unknown.
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { | ||||
|   /* Returns 0 if the normal is within tolerance */ | ||||
|   /* Returns 1 if the normal is not within tolerance, but direction is OK */ | ||||
|   /* Returns 2 if the normal is not within tolerance and backwards */ | ||||
|   /* Returns 4 if the status is unknown. */ | ||||
| void stl_fix_normal_directions(stl_file *stl) | ||||
| { | ||||
|  	// This may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
 | ||||
|   	if (stl->stats.number_of_facets == 0) | ||||
|   		return; | ||||
| 
 | ||||
|   stl_facet *facet; | ||||
| 	struct stl_normal { | ||||
|     	int         facet_num; | ||||
|     	stl_normal *next; | ||||
|   	}; | ||||
| 
 | ||||
|   facet = &stl->facet_start[facet_num]; | ||||
|   	// Initialize linked list.
 | ||||
|   	boost::object_pool<stl_normal> pool; | ||||
|    	stl_normal *head = pool.construct(); | ||||
|   	stl_normal *tail = pool.construct(); | ||||
| 	head->next = tail; | ||||
| 	tail->next = tail; | ||||
| 
 | ||||
|   stl_normal normal; | ||||
|   stl_calculate_normal(normal, facet); | ||||
|   stl_normalize_vector(normal); | ||||
|   stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); | ||||
| 	// Initialize list that keeps track of already fixed facets.
 | ||||
| 	std::vector<char> norm_sw(stl->stats.number_of_facets, 0); | ||||
| 	// Initialize list that keeps track of reversed facets.
 | ||||
| 	std::vector<int>  reversed_ids(stl->stats.number_of_facets, 0); | ||||
| 
 | ||||
|   const float eps = 0.001f; | ||||
|   if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { | ||||
|     /* It is not really necessary to change the values here */ | ||||
|     /* but just for consistency, I will. */ | ||||
|     facet->normal = normal; | ||||
|     return 0; | ||||
|   } | ||||
|   	int facet_num = 0; | ||||
|   	int reversed_count = 0; | ||||
|   	// If normal vector is not within tolerance and backwards:
 | ||||
|     // Arbitrarily starts at face 0.  If this one is wrong, we're screwed. Thankfully, the chances
 | ||||
|     // of it being wrong randomly are low if most of the triangles are right:
 | ||||
|   	if (check_normal_vector(stl, 0, 0)) { | ||||
|     	reverse_facet(stl, 0); | ||||
|       	reversed_ids[reversed_count ++] = 0; | ||||
|   	} | ||||
| 
 | ||||
|   stl_normal test_norm = facet->normal; | ||||
|   stl_normalize_vector(test_norm); | ||||
|   normal_dif = (normal - test_norm).cwiseAbs(); | ||||
|   if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { | ||||
|     if(normal_fix_flag) { | ||||
|       facet->normal = normal; | ||||
|       stl->stats.normals_fixed += 1; | ||||
|     } | ||||
|     return 1; | ||||
|   } | ||||
|   	// Say that we've fixed this facet:
 | ||||
|   	norm_sw[facet_num] = 1; | ||||
| 	int checked = 1; | ||||
| 
 | ||||
|   test_norm *= -1.f; | ||||
|   normal_dif = (normal - test_norm).cwiseAbs(); | ||||
|   if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { | ||||
|     // Facet is backwards.
 | ||||
|     if(normal_fix_flag) { | ||||
|       facet->normal = normal; | ||||
|       stl->stats.normals_fixed += 1; | ||||
|     } | ||||
|     return 2; | ||||
|   } | ||||
|   if(normal_fix_flag) { | ||||
|     facet->normal = normal; | ||||
|     stl->stats.normals_fixed += 1; | ||||
|   } | ||||
|   return 4; | ||||
|   	for (;;) { | ||||
|     	// Add neighbors_to_list. Add unconnected neighbors to the list.
 | ||||
|     	bool force_exit = false; | ||||
|     	for (int j = 0; j < 3; ++ j) { | ||||
|       		// Reverse the neighboring facets if necessary.
 | ||||
|       		if (stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { | ||||
|         		// If the facet has a neighbor that is -1, it means that edge isn't shared by another facet
 | ||||
|         		if (stl->neighbors_start[facet_num].neighbor[j] != -1) { | ||||
|             		if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { | ||||
|                 		// trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206)
 | ||||
|                 		for (int id = reversed_count - 1; id >= 0; -- id) | ||||
|                     		reverse_facet(stl, reversed_ids[id]); | ||||
|                 		force_exit = true; | ||||
|                 		break; | ||||
|             		} | ||||
|             		reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); | ||||
|             		reversed_ids[reversed_count ++] = stl->neighbors_start[facet_num].neighbor[j]; | ||||
|         		} | ||||
|       		} | ||||
|       		// If this edge of the facet is connected:
 | ||||
|       		if (stl->neighbors_start[facet_num].neighbor[j] != -1) { | ||||
|         		// If we haven't fixed this facet yet, add it to the list:
 | ||||
|         		if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { | ||||
| 	          		// Add node to beginning of list.
 | ||||
| 	          		stl_normal *newn = pool.construct(); | ||||
| 	          		newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; | ||||
| 	          		newn->next = head->next; | ||||
| 	          		head->next = newn; | ||||
| 	        	} | ||||
| 	      	} | ||||
| 	    } | ||||
| 
 | ||||
|     	// an error occourred, quit the for loop and exit
 | ||||
|     	if (force_exit) | ||||
|     		break; | ||||
| 
 | ||||
|     	// Get next facet to fix from top of list.
 | ||||
|     	if (head->next != tail) { | ||||
|       		facet_num = head->next->facet_num; | ||||
|       		if (norm_sw[facet_num] != 1) { // If facet is in list mutiple times
 | ||||
|         		norm_sw[facet_num] = 1; // Record this one as being fixed.
 | ||||
|         		++ checked; | ||||
|       		} | ||||
|       		stl_normal *temp = head->next;	// Delete this facet from the list.
 | ||||
|       		head->next = head->next->next; | ||||
|       		// pool.destroy(temp);
 | ||||
|     	} else { // If we ran out of facets to fix: All of the facets in this part have been fixed.
 | ||||
|       		++ stl->stats.number_of_parts; | ||||
|       		if (checked >= stl->stats.number_of_facets) | ||||
|         		// All of the facets have been checked.  Bail out.
 | ||||
|         		break; | ||||
|     		// There is another part here.  Find it and continue.
 | ||||
|     		for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|       			if (norm_sw[i] == 0) { | ||||
|         			// This is the first facet of the next part.
 | ||||
|         			facet_num = i; | ||||
|         			if (check_normal_vector(stl, i, 0)) { | ||||
|             			reverse_facet(stl, i); | ||||
|             			reversed_ids[reversed_count++] = i; | ||||
|         			} | ||||
|         			norm_sw[facet_num] = 1; | ||||
|         			++ checked; | ||||
|         			break; | ||||
|       			} | ||||
|     	} | ||||
|   	} | ||||
| 
 | ||||
| 	// pool.destroy(head);
 | ||||
| 	// pool.destroy(tail);
 | ||||
| } | ||||
| 
 | ||||
| void stl_fix_normal_values(stl_file *stl) { | ||||
|   int i; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     stl_check_normal_vector(stl, i, 1); | ||||
|   } | ||||
| void stl_fix_normal_values(stl_file *stl) | ||||
| { | ||||
| 	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     	check_normal_vector(stl, i, 1); | ||||
| } | ||||
| 
 | ||||
| void stl_reverse_all_facets(stl_file *stl) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 
 | ||||
|   stl_normal normal; | ||||
|   for(int i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     stl_reverse_facet(stl, i); | ||||
|     stl_calculate_normal(normal, &stl->facet_start[i]); | ||||
|     stl_normalize_vector(normal); | ||||
|     stl->facet_start[i].normal = normal; | ||||
|   } | ||||
| 	stl_normal normal; | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     	reverse_facet(stl, i); | ||||
|     	stl_calculate_normal(normal, &stl->facet_start[i]); | ||||
|     	stl_normalize_vector(normal); | ||||
|     	stl->facet_start[i].normal = normal; | ||||
|   	} | ||||
| } | ||||
|  |  | |||
|  | @ -23,242 +23,237 @@ | |||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| 
 | ||||
| #include "stl.h" | ||||
| 
 | ||||
| void | ||||
| stl_invalidate_shared_vertices(stl_file *stl) { | ||||
|   if (stl->error) return; | ||||
| void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) | ||||
| { | ||||
| 	// 3 indices to vertex per face
 | ||||
| 	its.indices.assign(stl->stats.number_of_facets, stl_triangle_vertex_indices(-1, -1, -1)); | ||||
| 	// Shared vertices (3D coordinates)
 | ||||
| 	its.vertices.clear(); | ||||
| 	its.vertices.reserve(stl->stats.number_of_facets / 2); | ||||
| 
 | ||||
|   if (stl->v_indices != NULL) { | ||||
|     free(stl->v_indices); | ||||
|     stl->v_indices = NULL; | ||||
|   } | ||||
|   if (stl->v_shared != NULL) { | ||||
|     free(stl->v_shared); | ||||
|     stl->v_shared = NULL; | ||||
|   } | ||||
| 	// A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop
 | ||||
| 	// while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal
 | ||||
| 	// are marked with a unique fan_traversal_stamp.
 | ||||
| 	unsigned int			  fan_traversal_stamp = 0; | ||||
| 	std::vector<unsigned int> fan_traversal_facet_visited(stl->stats.number_of_facets, 0); | ||||
| 
 | ||||
| 	for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) { | ||||
| 		for (int j = 0; j < 3; ++ j) { | ||||
| 			if (its.indices[facet_idx][j] != -1) | ||||
| 				// Shared vertex was already assigned.
 | ||||
| 				continue; | ||||
| 			// Create a new shared vertex.
 | ||||
| 			its.vertices.emplace_back(stl->facet_start[facet_idx].vertex[j]); | ||||
| 			// Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan.
 | ||||
| 			int  facet_in_fan_idx 	= facet_idx; | ||||
| 			bool edge_direction 	= false; | ||||
| 			bool traversal_reversed = false; | ||||
| 			int  vnot      			= (j + 2) % 3; | ||||
| 			// Increase the 
 | ||||
| 			++ fan_traversal_stamp; | ||||
| 			for (;;) { | ||||
| 				// Next edge on facet_in_fan_idx to be traversed. The edge is indexed by its starting vertex index.
 | ||||
| 				int next_edge    = 0; | ||||
| 				// Vertex index in facet_in_fan_idx, which is being pivoted around, and which is being assigned a new shared vertex.
 | ||||
| 				int pivot_vertex = 0; | ||||
| 				if (vnot > 2) { | ||||
| 					// The edge of facet_in_fan_idx opposite to vnot is equally oriented, therefore
 | ||||
| 					// the neighboring facet is flipped.
 | ||||
| 			  		if (! edge_direction) { | ||||
| 			    		pivot_vertex = (vnot + 2) % 3; | ||||
| 			    		next_edge    = pivot_vertex;			    		 | ||||
| 			  		} else { | ||||
| 			    		pivot_vertex = (vnot + 1) % 3; | ||||
| 			    		next_edge    = vnot % 3; | ||||
| 			  		} | ||||
| 			  		edge_direction = ! edge_direction; | ||||
| 				} else { | ||||
| 					// The neighboring facet is correctly oriented.
 | ||||
| 			  		if (! edge_direction) { | ||||
| 			    		pivot_vertex = (vnot + 1) % 3; | ||||
| 			    		next_edge    = vnot; | ||||
| 			  		} else { | ||||
| 			    		pivot_vertex = (vnot + 2) % 3; | ||||
| 			    		next_edge    = pivot_vertex; | ||||
| 			  		} | ||||
| 				} | ||||
| 				its.indices[facet_in_fan_idx][pivot_vertex] = its.vertices.size() - 1; | ||||
| 				fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp; | ||||
| 
 | ||||
| 				// next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge!
 | ||||
| 				int next_facet = stl->neighbors_start[facet_in_fan_idx].neighbor[next_edge]; | ||||
| 				if (next_facet == -1) { | ||||
| 					// No neighbor going in the current direction.
 | ||||
| 					if (traversal_reversed) { | ||||
| 						// Went to one limit, then turned back and reached the other limit. Quit the fan traversal.
 | ||||
| 					    break; | ||||
| 					} else { | ||||
| 						// Reached the first limit. Now try to reverse and traverse up to the other limit.
 | ||||
| 					    edge_direction        = true; | ||||
| 					    vnot 	         	  = (j + 1) % 3; | ||||
| 					    traversal_reversed    = true; | ||||
| 				    	facet_in_fan_idx      = facet_idx; | ||||
| 					} | ||||
| 				} else if (next_facet == facet_idx) { | ||||
| 					// Traversed a closed fan all around.
 | ||||
| //					assert(! traversal_reversed);
 | ||||
| 					break; | ||||
| 				} else if (next_facet >= (int)stl->stats.number_of_facets) { | ||||
| 					// The mesh is not valid!
 | ||||
| 					// assert(false);
 | ||||
| 					break; | ||||
| 				} else if (fan_traversal_facet_visited[next_facet] == fan_traversal_stamp) { | ||||
| 					// Traversed a closed fan all around, but did not reach the starting face.
 | ||||
| 					// This indicates an invalid geometry (non-manifold).
 | ||||
| 					//assert(false);
 | ||||
| 					break; | ||||
| 				} else { | ||||
| 					// Continue traversal.
 | ||||
| 					// next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge!
 | ||||
| 					vnot = stl->neighbors_start[facet_in_fan_idx].which_vertex_not[next_edge]; | ||||
| 					facet_in_fan_idx = next_facet; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_generate_shared_vertices(stl_file *stl) { | ||||
|   int i; | ||||
|   int j; | ||||
|   int first_facet; | ||||
|   int direction; | ||||
|   int facet_num; | ||||
|   int vnot; | ||||
|   int next_edge; | ||||
|   int pivot_vertex; | ||||
|   int next_facet; | ||||
|   int reversed; | ||||
| bool its_write_off(const indexed_triangle_set &its, const char *file) | ||||
| { | ||||
| 	/* Open the file */ | ||||
| 	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
| 	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 	fprintf(fp, "OFF\n"); | ||||
| 	fprintf(fp, "%d %d 0\n", (int)its.vertices.size(), (int)its.indices.size()); | ||||
| 	for (int i = 0; i < its.vertices.size(); ++ i) | ||||
| 		fprintf(fp, "\t%f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); | ||||
| 	for (uint32_t i = 0; i < its.indices.size(); ++ i) | ||||
| 		fprintf(fp, "\t3 %d %d %d\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); | ||||
| 	fclose(fp); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
|   /* make sure this function is idempotent and does not leak memory */ | ||||
|   stl_invalidate_shared_vertices(stl); | ||||
| bool its_write_vrml(const indexed_triangle_set &its, const char *file) | ||||
| { | ||||
| 	/* Open the file */ | ||||
|   	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
| 	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_vrml: Couldn't open " << file << " for writing"; | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
|   stl->v_indices = (v_indices_struct*) | ||||
|                    calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); | ||||
|   if(stl->v_indices == NULL) perror("stl_generate_shared_vertices"); | ||||
|   stl->v_shared = (stl_vertex*) | ||||
|                   calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); | ||||
|   if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); | ||||
|   stl->stats.shared_malloced = stl->stats.number_of_facets / 2; | ||||
|   stl->stats.shared_vertices = 0; | ||||
| 	fprintf(fp, "#VRML V1.0 ascii\n\n"); | ||||
| 	fprintf(fp, "Separator {\n"); | ||||
| 	fprintf(fp, "\tDEF STLShape ShapeHints {\n"); | ||||
| 	fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); | ||||
| 	fprintf(fp, "\t\tfaceType CONVEX\n"); | ||||
| 	fprintf(fp, "\t\tshapeType SOLID\n"); | ||||
| 	fprintf(fp, "\t\tcreaseAngle 0.0\n"); | ||||
| 	fprintf(fp, "\t}\n"); | ||||
| 	fprintf(fp, "\tDEF STLModel Separator {\n"); | ||||
| 	fprintf(fp, "\t\tDEF STLColor Material {\n"); | ||||
| 	fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); | ||||
| 	fprintf(fp, "\t\t}\n"); | ||||
| 	fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); | ||||
| 	fprintf(fp, "\t\t\tpoint [\n"); | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     stl->v_indices[i].vertex[0] = -1; | ||||
|     stl->v_indices[i].vertex[1] = -1; | ||||
|     stl->v_indices[i].vertex[2] = -1; | ||||
|   } | ||||
| 	int i = 0; | ||||
| 	for (; i + 1 < its.vertices.size(); ++ i) | ||||
| 		fprintf(fp, "\t\t\t\t%f %f %f,\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); | ||||
| 	fprintf(fp, "\t\t\t\t%f %f %f]\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); | ||||
| 	fprintf(fp, "\t\t}\n"); | ||||
| 	fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); | ||||
| 	fprintf(fp, "\t\t\tcoordIndex [\n"); | ||||
| 
 | ||||
| 	for (size_t i = 0; i + 1 < its.indices.size(); ++ i) | ||||
| 		fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); | ||||
| 	fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); | ||||
| 	fprintf(fp, "\t\t}\n"); | ||||
| 	fprintf(fp, "\t}\n"); | ||||
| 	fprintf(fp, "}\n"); | ||||
| 	fclose(fp); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool its_write_obj(const indexed_triangle_set &its, const char *file) | ||||
| { | ||||
| 
 | ||||
|   	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
|   	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; | ||||
|     	return false; | ||||
|   	} | ||||
| 
 | ||||
| 	for (size_t i = 0; i < its.vertices.size(); ++ i) | ||||
|     	fprintf(fp, "v %f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); | ||||
|   	for (size_t i = 0; i < its.indices.size(); ++ i) | ||||
|     	fprintf(fp, "f %d %d %d\n", its.indices[i][0]+1, its.indices[i][1]+1, its.indices[i][2]+1); | ||||
|   	fclose(fp); | ||||
|   	return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     first_facet = i; | ||||
|     for(j = 0; j < 3; j++) { | ||||
|       if(stl->v_indices[i].vertex[j] != -1) { | ||||
|         continue; | ||||
|       } | ||||
|       if(stl->stats.shared_vertices == stl->stats.shared_malloced) { | ||||
|         stl->stats.shared_malloced += 1024; | ||||
|         stl->v_shared = (stl_vertex*)realloc(stl->v_shared, | ||||
|                                              stl->stats.shared_malloced * sizeof(stl_vertex)); | ||||
|         if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); | ||||
|       } | ||||
| // Check validity of the mesh, assert on error.
 | ||||
| bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) | ||||
| { | ||||
| 	assert(! stl->facet_start.empty()); | ||||
| 	assert(stl->facet_start.size() == stl->stats.number_of_facets); | ||||
| 	assert(stl->neighbors_start.size() == stl->stats.number_of_facets); | ||||
| 	assert(stl->facet_start.size() == stl->neighbors_start.size()); | ||||
| 	assert(! stl->neighbors_start.empty()); | ||||
| 	assert((its.indices.empty()) == (its.vertices.empty())); | ||||
| 	assert(stl->stats.number_of_facets > 0); | ||||
| 	assert(its.vertices.empty() || its.indices.size() == stl->stats.number_of_facets); | ||||
| 
 | ||||
|       stl->v_shared[stl->stats.shared_vertices] = | ||||
|         stl->facet_start[i].vertex[j]; | ||||
| 
 | ||||
|       direction = 0; | ||||
|       reversed = 0; | ||||
|       facet_num = i; | ||||
|       vnot = (j + 2) % 3; | ||||
| 
 | ||||
|       for(;;) { | ||||
|         if(vnot > 2) { | ||||
|           if(direction == 0) { | ||||
|             pivot_vertex = (vnot + 2) % 3; | ||||
|             next_edge = pivot_vertex; | ||||
|             direction = 1; | ||||
|           } else { | ||||
|             pivot_vertex = (vnot + 1) % 3; | ||||
|             next_edge = vnot % 3; | ||||
|             direction = 0; | ||||
|           } | ||||
|         } else { | ||||
|           if(direction == 0) { | ||||
|             pivot_vertex = (vnot + 1) % 3; | ||||
|             next_edge = vnot; | ||||
|           } else { | ||||
|             pivot_vertex = (vnot + 2) % 3; | ||||
|             next_edge = pivot_vertex; | ||||
|           } | ||||
| #ifdef _DEBUG | ||||
|     // Verify validity of neighborship data.
 | ||||
|     for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { | ||||
|         const stl_neighbors &nbr 		= stl->neighbors_start[facet_idx]; | ||||
|         const int 			*vertices 	= its.indices.empty() ? nullptr : its.indices[facet_idx].data(); | ||||
|         for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { | ||||
|             int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; | ||||
|             assert(nbr_face < (int)stl->stats.number_of_facets); | ||||
|             if (nbr_face != -1) { | ||||
|             	int nbr_vnot = nbr.which_vertex_not[nbr_idx]; | ||||
| 				assert(nbr_vnot >= 0 && nbr_vnot < 6); | ||||
| 				// Neighbor of the neighbor is the original face.
 | ||||
| 				assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx); | ||||
| 				int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3]; | ||||
| 				assert(vnot_back >= 0 && vnot_back < 6); | ||||
| 				assert((nbr_vnot < 3) == (vnot_back < 3)); | ||||
| 				assert(vnot_back % 3 == (nbr_idx + 2) % 3); | ||||
| 				if (vertices != nullptr) { | ||||
| 					// Has shared vertices.
 | ||||
| 	            	if (nbr_vnot < 3) { | ||||
| 	            		// Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented.
 | ||||
| 						assert((its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[nbr_idx])); | ||||
| 					} else { | ||||
| 	            		// Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped.
 | ||||
| 						assert((its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[nbr_idx])); | ||||
| 					} | ||||
| 				} | ||||
|             } | ||||
|         } | ||||
|         stl->v_indices[facet_num].vertex[pivot_vertex] = | ||||
|           stl->stats.shared_vertices; | ||||
| 
 | ||||
|         next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; | ||||
|         if(next_facet == -1) { | ||||
|           if(reversed) { | ||||
|             break; | ||||
|           } else { | ||||
|             direction = 1; | ||||
|             vnot = (j + 1) % 3; | ||||
|             reversed = 1; | ||||
|             facet_num = first_facet; | ||||
|           } | ||||
|         } else if(next_facet != first_facet) { | ||||
|           vnot = stl->neighbors_start[facet_num]. | ||||
|                  which_vertex_not[next_edge]; | ||||
|           facet_num = next_facet; | ||||
|         } else { | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       stl->stats.shared_vertices += 1; | ||||
|     } | ||||
|   } | ||||
| #endif /* _DEBUG */ | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_off(stl_file *stl, const char *file) { | ||||
|   int i; | ||||
|   FILE      *fp; | ||||
|   char      *error_msg; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "w"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   fprintf(fp, "OFF\n"); | ||||
|   fprintf(fp, "%d %d 0\n", | ||||
|           stl->stats.shared_vertices, stl->stats.number_of_facets); | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.shared_vertices; i++) { | ||||
|     fprintf(fp, "\t%f %f %f\n", | ||||
|             stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); | ||||
|   } | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], | ||||
|             stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); | ||||
|   } | ||||
|   fclose(fp); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_vrml(stl_file *stl, const char *file) { | ||||
|   int i; | ||||
|   FILE      *fp; | ||||
|   char      *error_msg; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "w"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   fprintf(fp, "#VRML V1.0 ascii\n\n"); | ||||
|   fprintf(fp, "Separator {\n"); | ||||
|   fprintf(fp, "\tDEF STLShape ShapeHints {\n"); | ||||
|   fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); | ||||
|   fprintf(fp, "\t\tfaceType CONVEX\n"); | ||||
|   fprintf(fp, "\t\tshapeType SOLID\n"); | ||||
|   fprintf(fp, "\t\tcreaseAngle 0.0\n"); | ||||
|   fprintf(fp, "\t}\n"); | ||||
|   fprintf(fp, "\tDEF STLModel Separator {\n"); | ||||
|   fprintf(fp, "\t\tDEF STLColor Material {\n"); | ||||
|   fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); | ||||
|   fprintf(fp, "\t\t}\n"); | ||||
|   fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); | ||||
|   fprintf(fp, "\t\t\tpoint [\n"); | ||||
| 
 | ||||
|   for(i = 0; i < (stl->stats.shared_vertices - 1); i++) { | ||||
|     fprintf(fp, "\t\t\t\t%f %f %f,\n", | ||||
|             stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); | ||||
|   } | ||||
|   fprintf(fp, "\t\t\t\t%f %f %f]\n", | ||||
|           stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); | ||||
|   fprintf(fp, "\t\t}\n"); | ||||
|   fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); | ||||
|   fprintf(fp, "\t\t\tcoordIndex [\n"); | ||||
| 
 | ||||
|   for(i = 0; i < (stl->stats.number_of_facets - 1); i++) { | ||||
|     fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], | ||||
|             stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); | ||||
|   } | ||||
|   fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], | ||||
|           stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); | ||||
|   fprintf(fp, "\t\t}\n"); | ||||
|   fprintf(fp, "\t}\n"); | ||||
|   fprintf(fp, "}\n"); | ||||
|   fclose(fp); | ||||
| } | ||||
| 
 | ||||
| void stl_write_obj (stl_file *stl, const char *file) { | ||||
|   int i; | ||||
|   FILE* fp; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "w"); | ||||
|   if (fp == NULL) { | ||||
|     char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   for (i = 0; i < stl->stats.shared_vertices; i++) { | ||||
|     fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); | ||||
|   } | ||||
|   for (i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); | ||||
|   } | ||||
| 
 | ||||
|   fclose(fp); | ||||
| // Check validity of the mesh, assert on error.
 | ||||
| bool stl_validate(const stl_file *stl) | ||||
| { | ||||
| 	indexed_triangle_set its; | ||||
| 	return stl_validate(stl, its); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										287
									
								
								src/admesh/stl.h
									
										
									
									
									
								
							
							
						
						
									
										287
									
								
								src/admesh/stl.h
									
										
									
									
									
								
							|  | @ -27,6 +27,7 @@ | |||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <Eigen/Geometry>  | ||||
| 
 | ||||
| // Size of the binary STL header, free form.
 | ||||
|  | @ -40,22 +41,23 @@ | |||
| 
 | ||||
| typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_vertex; | ||||
| typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_normal; | ||||
| typedef Eigen::Matrix<int,   3, 1, Eigen::DontAlign> stl_triangle_vertex_indices; | ||||
| static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); | ||||
| static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); | ||||
| 
 | ||||
| struct stl_facet { | ||||
|   stl_normal normal; | ||||
|   stl_vertex vertex[3]; | ||||
|   char       extra[2]; | ||||
| 	stl_normal normal; | ||||
| 	stl_vertex vertex[3]; | ||||
| 	char       extra[2]; | ||||
| 
 | ||||
|   stl_facet  rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) { | ||||
|     stl_facet out; | ||||
|     out.normal    = rot * this->normal; | ||||
|     out.vertex[0] = rot * this->vertex[0]; | ||||
|     out.vertex[1] = rot * this->vertex[1]; | ||||
|     out.vertex[2] = rot * this->vertex[2]; | ||||
|     return out; | ||||
|   } | ||||
| 	stl_facet  rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) const { | ||||
| 		stl_facet out; | ||||
| 		out.normal    = rot * this->normal; | ||||
| 		out.vertex[0] = rot * this->vertex[0]; | ||||
| 		out.vertex[1] = rot * this->vertex[1]; | ||||
| 		out.vertex[2] = rot * this->vertex[2]; | ||||
| 		return out; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| #define SIZEOF_STL_FACET       50 | ||||
|  | @ -67,104 +69,94 @@ static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrec | |||
| 
 | ||||
| typedef enum {binary, ascii, inmemory} stl_type; | ||||
| 
 | ||||
| typedef struct { | ||||
|   stl_vertex p1; | ||||
|   stl_vertex p2; | ||||
|   int        facet_number; | ||||
| } stl_edge; | ||||
| struct stl_neighbors { | ||||
|   	stl_neighbors() { reset(); } | ||||
|   	void reset() { | ||||
|   		neighbor[0] = -1; | ||||
|   		neighbor[1] = -1; | ||||
|   		neighbor[2] = -1; | ||||
|   		which_vertex_not[0] = -1; | ||||
|   		which_vertex_not[1] = -1; | ||||
|   		which_vertex_not[2] = -1; | ||||
|   	} | ||||
|   	int num_neighbors_missing() const { return (this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1); } | ||||
|   	int num_neighbors() const { return 3 - this->num_neighbors_missing(); } | ||||
| 
 | ||||
| typedef struct stl_hash_edge { | ||||
|   // Key of a hash edge: sorted vertices of the edge.
 | ||||
|   uint32_t       key[6]; | ||||
|   // Compare two keys.
 | ||||
|   bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } | ||||
|   bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } | ||||
|   int  hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11  + key[4] / 7 + key[5] / 3)) % M; } | ||||
|   // Index of a facet owning this edge.
 | ||||
|   int            facet_number; | ||||
|   // Index of this edge inside the facet with an index of facet_number.
 | ||||
|   // If this edge is stored backwards, which_edge is increased by 3.
 | ||||
|   int            which_edge; | ||||
|   struct stl_hash_edge  *next; | ||||
| } stl_hash_edge; | ||||
|   	// Index of a neighbor facet.
 | ||||
|   	int   neighbor[3]; | ||||
|   	// Index of an opposite vertex at the neighbor face.
 | ||||
|   	char  which_vertex_not[3]; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|   // Index of a neighbor facet.
 | ||||
|   int   neighbor[3]; | ||||
|   // Index of an opposite vertex at the neighbor face.
 | ||||
|   char  which_vertex_not[3]; | ||||
| } stl_neighbors; | ||||
| struct stl_stats { | ||||
| 	stl_stats() { this->reset(); } | ||||
| 	void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; } | ||||
| 	char          header[81]; | ||||
| 	stl_type      type; | ||||
| 	uint32_t      number_of_facets; | ||||
| 	stl_vertex    max; | ||||
| 	stl_vertex    min; | ||||
| 	stl_vertex    size; | ||||
| 	float         bounding_diameter; | ||||
| 	float         shortest_edge; | ||||
| 	float         volume; | ||||
| 	int           connected_edges; | ||||
| 	int           connected_facets_1_edge; | ||||
| 	int           connected_facets_2_edge; | ||||
| 	int           connected_facets_3_edge; | ||||
| 	int           facets_w_1_bad_edge; | ||||
| 	int           facets_w_2_bad_edge; | ||||
| 	int           facets_w_3_bad_edge; | ||||
| 	int           original_num_facets; | ||||
| 	int           edges_fixed; | ||||
| 	int           degenerate_facets; | ||||
| 	int           facets_removed; | ||||
| 	int           facets_added; | ||||
| 	int           facets_reversed; | ||||
| 	int           backwards_edges; | ||||
| 	int           normals_fixed; | ||||
| 	int           number_of_parts; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|   int   vertex[3]; | ||||
| } v_indices_struct; | ||||
| struct stl_file { | ||||
| 	stl_file() {} | ||||
| 
 | ||||
| typedef struct { | ||||
|   char          header[81]; | ||||
|   stl_type      type; | ||||
|   uint32_t      number_of_facets; | ||||
|   stl_vertex    max; | ||||
|   stl_vertex    min; | ||||
|   stl_vertex    size; | ||||
|   float         bounding_diameter; | ||||
|   float         shortest_edge; | ||||
|   float         volume; | ||||
|   unsigned      number_of_blocks; | ||||
|   int           connected_edges; | ||||
|   int           connected_facets_1_edge; | ||||
|   int           connected_facets_2_edge; | ||||
|   int           connected_facets_3_edge; | ||||
|   int           facets_w_1_bad_edge; | ||||
|   int           facets_w_2_bad_edge; | ||||
|   int           facets_w_3_bad_edge; | ||||
|   int           original_num_facets; | ||||
|   int           edges_fixed; | ||||
|   int           degenerate_facets; | ||||
|   int           facets_removed; | ||||
|   int           facets_added; | ||||
|   int           facets_reversed; | ||||
|   int           backwards_edges; | ||||
|   int           normals_fixed; | ||||
|   int           number_of_parts; | ||||
|   int           malloced; | ||||
|   int           freed; | ||||
|   int           facets_malloced; | ||||
|   int           collisions; | ||||
|   int           shared_vertices; | ||||
|   int           shared_malloced; | ||||
| } stl_stats; | ||||
| 	void clear() { | ||||
| 		this->facet_start.clear(); | ||||
| 		this->neighbors_start.clear(); | ||||
| 		this->stats.reset(); | ||||
| 	} | ||||
| 
 | ||||
| typedef struct { | ||||
|   FILE          *fp; | ||||
|   stl_facet     *facet_start; | ||||
|   stl_hash_edge **heads; | ||||
|   stl_hash_edge *tail; | ||||
|   int           M; | ||||
|   stl_neighbors *neighbors_start; | ||||
|   v_indices_struct *v_indices; | ||||
|   stl_vertex    *v_shared; | ||||
|   stl_stats     stats; | ||||
|   char          error; | ||||
| } stl_file; | ||||
| 	std::vector<stl_facet>     		facet_start; | ||||
| 	std::vector<stl_neighbors> 		neighbors_start; | ||||
| 	// Statistics
 | ||||
| 	stl_stats     					stats; | ||||
| }; | ||||
| 
 | ||||
| struct indexed_triangle_set | ||||
| { | ||||
| 	indexed_triangle_set() {} | ||||
| 
 | ||||
| extern void stl_open(stl_file *stl, const char *file); | ||||
| extern void stl_close(stl_file *stl); | ||||
| 	void clear() { indices.clear(); vertices.clear(); } | ||||
| 
 | ||||
| 	std::vector<stl_triangle_vertex_indices> 	indices; | ||||
| 	std::vector<stl_vertex>       				vertices; | ||||
| 	//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
 | ||||
| 	//std::vector<stl_normal> 					normals
 | ||||
| }; | ||||
| 
 | ||||
| extern bool stl_open(stl_file *stl, const char *file); | ||||
| extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); | ||||
| extern void stl_print_neighbors(stl_file *stl, char *file); | ||||
| extern void stl_put_little_int(FILE *fp, int value_in); | ||||
| extern void stl_put_little_float(FILE *fp, float value_in); | ||||
| extern void stl_write_ascii(stl_file *stl, const char *file, const char *label); | ||||
| extern void stl_write_binary(stl_file *stl, const char *file, const char *label); | ||||
| extern void stl_write_binary_block(stl_file *stl, FILE *fp); | ||||
| extern bool stl_print_neighbors(stl_file *stl, char *file); | ||||
| extern bool stl_write_ascii(stl_file *stl, const char *file, const char *label); | ||||
| extern bool stl_write_binary(stl_file *stl, const char *file, const char *label); | ||||
| extern void stl_check_facets_exact(stl_file *stl); | ||||
| extern void stl_check_facets_nearby(stl_file *stl, float tolerance); | ||||
| extern void stl_remove_unconnected_facets(stl_file *stl); | ||||
| extern void stl_write_vertex(stl_file *stl, int facet, int vertex); | ||||
| extern void stl_write_facet(stl_file *stl, char *label, int facet); | ||||
| extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge); | ||||
| extern void stl_write_neighbor(stl_file *stl, int facet); | ||||
| extern void stl_write_quad_object(stl_file *stl, char *file); | ||||
| extern bool stl_write_quad_object(stl_file *stl, char *file); | ||||
| extern void stl_verify_neighbors(stl_file *stl); | ||||
| extern void stl_fill_holes(stl_file *stl); | ||||
| extern void stl_fix_normal_directions(stl_file *stl); | ||||
|  | @ -186,36 +178,30 @@ extern void stl_get_size(stl_file *stl); | |||
| template<typename T> | ||||
| extern void stl_transform(stl_file *stl, T *trafo3x4) | ||||
| { | ||||
|   if (stl->error) | ||||
|     return; | ||||
| 	for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { | ||||
| 		stl_facet &face = stl->facet_start[i_face]; | ||||
| 		for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) { | ||||
| 			stl_vertex &v_dst = face.vertex[i_vertex]; | ||||
| 			stl_vertex  v_src = v_dst; | ||||
| 			v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2]  * v_src(2) + trafo3x4[3]); | ||||
| 			v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6]  * v_src(2) + trafo3x4[7]); | ||||
| 			v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); | ||||
| 		} | ||||
| 		stl_vertex &v_dst = face.normal; | ||||
| 		stl_vertex  v_src = v_dst; | ||||
| 		v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2]  * v_src(2)); | ||||
| 		v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6]  * v_src(2)); | ||||
| 		v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2)); | ||||
| 	} | ||||
| 
 | ||||
|   for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { | ||||
|     stl_facet &face = stl->facet_start[i_face]; | ||||
|     for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) { | ||||
|       stl_vertex &v_dst = face.vertex[i_vertex]; | ||||
|       stl_vertex  v_src = v_dst; | ||||
|       v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2]  * v_src(2) + trafo3x4[3]); | ||||
|       v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6]  * v_src(2) + trafo3x4[7]); | ||||
|       v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); | ||||
|     } | ||||
|     stl_vertex &v_dst = face.normal; | ||||
|     stl_vertex  v_src = v_dst; | ||||
|     v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2]  * v_src(2)); | ||||
|     v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6]  * v_src(2)); | ||||
|     v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2)); | ||||
|   } | ||||
| 
 | ||||
|   stl_get_size(stl); | ||||
| 	stl_get_size(stl); | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t) | ||||
| { | ||||
| 	if (stl->error) | ||||
| 		return; | ||||
| 
 | ||||
| 	const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0); | ||||
| 	for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { | ||||
| 	for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		stl_facet &f = stl->facet_start[i]; | ||||
| 		for (size_t j = 0; j < 3; ++j) | ||||
| 			f.vertex[j] = (t * f.vertex[j].template cast<T>()).template cast<float>().eval(); | ||||
|  | @ -228,10 +214,7 @@ inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Aff | |||
| template<typename T> | ||||
| inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m) | ||||
| { | ||||
| 	if (stl->error) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { | ||||
| 	for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		stl_facet &f = stl->facet_start[i]; | ||||
| 		for (size_t j = 0; j < 3; ++j) | ||||
| 			f.vertex[j] = (m * f.vertex[j].template cast<T>()).template cast<float>().eval(); | ||||
|  | @ -241,13 +224,43 @@ inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::Don | |||
| 	stl_get_size(stl); | ||||
| } | ||||
| 
 | ||||
| extern void stl_open_merge(stl_file *stl, char *file); | ||||
| extern void stl_invalidate_shared_vertices(stl_file *stl); | ||||
| extern void stl_generate_shared_vertices(stl_file *stl); | ||||
| extern void stl_write_obj(stl_file *stl, const char *file); | ||||
| extern void stl_write_off(stl_file *stl, const char *file); | ||||
| extern void stl_write_dxf(stl_file *stl, const char *file, char *label); | ||||
| extern void stl_write_vrml(stl_file *stl, const char *file); | ||||
| 
 | ||||
| template<typename T> | ||||
| extern void its_transform(indexed_triangle_set &its, T *trafo3x4) | ||||
| { | ||||
| 	for (stl_vertex &v_dst : its.vertices) { | ||||
| 		stl_vertex  v_src = v_dst; | ||||
| 		v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2]  * v_src(2) + trafo3x4[3]); | ||||
| 		v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6]  * v_src(2) + trafo3x4[7]); | ||||
| 		v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| inline void its_transform(indexed_triangle_set &its, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t) | ||||
| { | ||||
| 	const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0); | ||||
| 	for (stl_vertex &v : its.vertices) | ||||
| 		v = (t * v.template cast<T>()).template cast<float>().eval(); | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m) | ||||
| { | ||||
| 	for (stl_vertex &v : its.vertices) | ||||
| 		v = (m * v.template cast<T>()).template cast<float>().eval(); | ||||
| } | ||||
| 
 | ||||
| extern void its_rotate_x(indexed_triangle_set &its, float angle); | ||||
| extern void its_rotate_y(indexed_triangle_set &its, float angle); | ||||
| extern void its_rotate_z(indexed_triangle_set &its, float angle); | ||||
| 
 | ||||
| extern void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its); | ||||
| extern bool its_write_obj(const indexed_triangle_set &its, const char *file); | ||||
| extern bool its_write_off(const indexed_triangle_set &its, const char *file); | ||||
| extern bool its_write_vrml(const indexed_triangle_set &its, const char *file); | ||||
| 
 | ||||
| extern bool stl_write_dxf(stl_file *stl, const char *file, char *label); | ||||
| inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) { | ||||
|   normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); | ||||
| } | ||||
|  | @ -258,24 +271,18 @@ inline void stl_normalize_vector(stl_normal &normal) { | |||
|   else | ||||
|     normal *= float(1.0 / length); | ||||
| } | ||||
| inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) { | ||||
|   return (a(0) != b(0)) ? (a(0) < b(0)) : | ||||
|         ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); | ||||
| } | ||||
| extern void stl_calculate_volume(stl_file *stl); | ||||
| 
 | ||||
| extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); | ||||
| extern void stl_repair(stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag); | ||||
| 
 | ||||
| extern void stl_initialize(stl_file *stl); | ||||
| extern void stl_count_facets(stl_file *stl, const char *file); | ||||
| extern void stl_allocate(stl_file *stl); | ||||
| extern void stl_read(stl_file *stl, int first_facet, bool first); | ||||
| extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); | ||||
| extern void stl_reallocate(stl_file *stl); | ||||
| extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); | ||||
| extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet); | ||||
| 
 | ||||
| extern void stl_clear_error(stl_file *stl); | ||||
| extern int stl_get_error(stl_file *stl); | ||||
| extern void stl_exit_on_error(stl_file *stl); | ||||
| // Validate the mesh, assert on error.
 | ||||
| extern bool stl_validate(const stl_file *stl); | ||||
| extern bool stl_validate(const stl_file *stl, const indexed_triangle_set &its); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -22,159 +22,86 @@ | |||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| #include <boost/predef/other/endian.h> | ||||
| 
 | ||||
| #include "stl.h" | ||||
| 
 | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| #include <boost/detail/endian.hpp> | ||||
| 
 | ||||
| #if !defined(SEEK_SET) | ||||
| #define SEEK_SET 0 | ||||
| #define SEEK_CUR 1 | ||||
| #define SEEK_END 2 | ||||
| #endif | ||||
| 
 | ||||
| void | ||||
| stl_stats_out(stl_file *stl, FILE *file, char *input_file) { | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* this is here for Slic3r, without our config.h
 | ||||
|      it won't use this part of the code anyway */ | ||||
| void stl_stats_out(stl_file *stl, FILE *file, char *input_file) | ||||
| { | ||||
|   	// This is here for Slic3r, without our config.h it won't use this part of the code anyway.
 | ||||
| #ifndef VERSION | ||||
| #define VERSION "unknown" | ||||
| #endif | ||||
|   fprintf(file, "\n\
 | ||||
| ================= Results produced by ADMesh version " VERSION " ================\n"); | ||||
|   fprintf(file, "\
 | ||||
| Input file         : %s\n", input_file); | ||||
|   if(stl->stats.type == binary) { | ||||
|     fprintf(file, "\
 | ||||
| File type          : Binary STL file\n"); | ||||
|   } else { | ||||
|     fprintf(file, "\
 | ||||
| File type          : ASCII STL file\n"); | ||||
|   } | ||||
|   fprintf(file, "\
 | ||||
| Header             : %s\n", stl->stats.header); | ||||
|   fprintf(file, "============== Size ==============\n"); | ||||
|   fprintf(file, "Min X = % f, Max X = % f\n", | ||||
|           stl->stats.min(0), stl->stats.max(0)); | ||||
|   fprintf(file, "Min Y = % f, Max Y = % f\n", | ||||
|           stl->stats.min(1), stl->stats.max(1)); | ||||
|   fprintf(file, "Min Z = % f, Max Z = % f\n", | ||||
|           stl->stats.min(2), stl->stats.max(2)); | ||||
| 
 | ||||
|   fprintf(file, "\
 | ||||
| ========= Facet Status ========== Original ============ Final ====\n"); | ||||
|   fprintf(file, "\
 | ||||
| Number of facets                 : %5d               %5d\n", | ||||
|           stl->stats.original_num_facets, stl->stats.number_of_facets); | ||||
|   fprintf(file, "\
 | ||||
| Facets with 1 disconnected edge  : %5d               %5d\n", | ||||
|           stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - | ||||
|           stl->stats.connected_facets_3_edge); | ||||
|   fprintf(file, "\
 | ||||
| Facets with 2 disconnected edges : %5d               %5d\n", | ||||
|           stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - | ||||
|           stl->stats.connected_facets_2_edge); | ||||
|   fprintf(file, "\
 | ||||
| Facets with 3 disconnected edges : %5d               %5d\n", | ||||
|           stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - | ||||
|           stl->stats.connected_facets_1_edge); | ||||
|   fprintf(file, "\
 | ||||
| Total disconnected facets        : %5d               %5d\n", | ||||
|           stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + | ||||
|           stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - | ||||
|           stl->stats.connected_facets_3_edge); | ||||
| 
 | ||||
|   fprintf(file, | ||||
|           "=== Processing Statistics ===     ===== Other Statistics =====\n"); | ||||
|   fprintf(file, "\
 | ||||
| Number of parts       : %5d        Volume   : % f\n", | ||||
|           stl->stats.number_of_parts, stl->stats.volume); | ||||
|   fprintf(file, "\
 | ||||
| Degenerate facets     : %5d\n", stl->stats.degenerate_facets); | ||||
|   fprintf(file, "\
 | ||||
| Edges fixed           : %5d\n", stl->stats.edges_fixed); | ||||
|   fprintf(file, "\
 | ||||
| Facets removed        : %5d\n", stl->stats.facets_removed); | ||||
|   fprintf(file, "\
 | ||||
| Facets added          : %5d\n", stl->stats.facets_added); | ||||
|   fprintf(file, "\
 | ||||
| Facets reversed       : %5d\n", stl->stats.facets_reversed); | ||||
|   fprintf(file, "\
 | ||||
| Backwards edges       : %5d\n", stl->stats.backwards_edges); | ||||
|   fprintf(file, "\
 | ||||
| Normals fixed         : %5d\n", stl->stats.normals_fixed); | ||||
|   	fprintf(file, "\n================= Results produced by ADMesh version " VERSION " ================\n"); | ||||
|   	fprintf(file, "Input file         : %s\n", input_file); | ||||
|   	if (stl->stats.type == binary) | ||||
|     	fprintf(file, "File type          : Binary STL file\n"); | ||||
|   	else | ||||
|     	fprintf(file, "File type          : ASCII STL file\n"); | ||||
|   	fprintf(file, "Header             : %s\n", stl->stats.header); | ||||
|   	fprintf(file, "============== Size ==============\n"); | ||||
|   	fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min(0), stl->stats.max(0)); | ||||
|   	fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min(1), stl->stats.max(1)); | ||||
|   	fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min(2), stl->stats.max(2)); | ||||
|   	fprintf(file, "========= Facet Status ========== Original ============ Final ====\n"); | ||||
|   	fprintf(file, "Number of facets                 : %5d               %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets); | ||||
|   	fprintf(file, "Facets with 1 disconnected edge  : %5d               %5d\n",  | ||||
|   		stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); | ||||
|   	fprintf(file, "Facets with 2 disconnected edges : %5d               %5d\n", | ||||
|     	stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); | ||||
|   	fprintf(file, "Facets with 3 disconnected edges : %5d               %5d\n", | ||||
|         stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); | ||||
|   	fprintf(file, "Total disconnected facets        : %5d               %5d\n", | ||||
| 		stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge); | ||||
|   	fprintf(file, "=== Processing Statistics ===     ===== Other Statistics =====\n"); | ||||
|   	fprintf(file, "Number of parts       : %5d        Volume   : %f\n", stl->stats.number_of_parts, stl->stats.volume); | ||||
|   	fprintf(file, "Degenerate facets     : %5d\n", stl->stats.degenerate_facets); | ||||
|   	fprintf(file, "Edges fixed           : %5d\n", stl->stats.edges_fixed); | ||||
|   	fprintf(file, "Facets removed        : %5d\n", stl->stats.facets_removed); | ||||
|   	fprintf(file, "Facets added          : %5d\n", stl->stats.facets_added); | ||||
|   	fprintf(file, "Facets reversed       : %5d\n", stl->stats.facets_reversed); | ||||
|   	fprintf(file, "Backwards edges       : %5d\n", stl->stats.backwards_edges); | ||||
|   	fprintf(file, "Normals fixed         : %5d\n", stl->stats.normals_fixed); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_ascii(stl_file *stl, const char *file, const char *label) { | ||||
|   int       i; | ||||
|   char      *error_msg; | ||||
| bool stl_write_ascii(stl_file *stl, const char *file, const char *label) | ||||
| { | ||||
| 	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
|   	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; | ||||
|     	return false; | ||||
|   	} | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 	fprintf(fp, "solid  %s\n", label); | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   FILE *fp = boost::nowide::fopen(file, "w"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		fprintf(fp, "  facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), stl->facet_start[i].normal(2)); | ||||
| 		fprintf(fp, "    outer loop\n"); | ||||
| 		fprintf(fp, "      vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2)); | ||||
| 		fprintf(fp, "      vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2)); | ||||
| 		fprintf(fp, "      vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); | ||||
| 		fprintf(fp, "    endloop\n"); | ||||
| 		fprintf(fp, "  endfacet\n"); | ||||
| 	} | ||||
| 
 | ||||
|   fprintf(fp, "solid  %s\n", label); | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     fprintf(fp, "  facet normal % .8E % .8E % .8E\n", | ||||
|             stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), | ||||
|             stl->facet_start[i].normal(2)); | ||||
|     fprintf(fp, "    outer loop\n"); | ||||
|     fprintf(fp, "      vertex % .8E % .8E % .8E\n", | ||||
|             stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), | ||||
|             stl->facet_start[i].vertex[0](2)); | ||||
|     fprintf(fp, "      vertex % .8E % .8E % .8E\n", | ||||
|             stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), | ||||
|             stl->facet_start[i].vertex[1](2)); | ||||
|     fprintf(fp, "      vertex % .8E % .8E % .8E\n", | ||||
|             stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), | ||||
|             stl->facet_start[i].vertex[2](2)); | ||||
|     fprintf(fp, "    endloop\n"); | ||||
|     fprintf(fp, "  endfacet\n"); | ||||
|   } | ||||
| 
 | ||||
|   fprintf(fp, "endsolid  %s\n", label); | ||||
| 
 | ||||
|   fclose(fp); | ||||
|   	fprintf(fp, "endsolid  %s\n", label); | ||||
|   	fclose(fp); | ||||
|   	return true; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_print_neighbors(stl_file *stl, char *file) { | ||||
|   int i; | ||||
|   FILE *fp; | ||||
|   char *error_msg; | ||||
| bool stl_print_neighbors(stl_file *stl, char *file) | ||||
| { | ||||
| 	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
| 	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_print_neighbors: Couldn't open " << file << " for writing"; | ||||
|     	return false; | ||||
|   	} | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "w"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_print_neighbors: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     	fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", | ||||
|             i, | ||||
|             stl->neighbors_start[i].neighbor[0], | ||||
|             (int)stl->neighbors_start[i].which_vertex_not[0], | ||||
|  | @ -182,234 +109,142 @@ stl_print_neighbors(stl_file *stl, char *file) { | |||
|             (int)stl->neighbors_start[i].which_vertex_not[1], | ||||
|             stl->neighbors_start[i].neighbor[2], | ||||
|             (int)stl->neighbors_start[i].which_vertex_not[2]); | ||||
|   } | ||||
|   fclose(fp); | ||||
|   	} | ||||
|   	fclose(fp); | ||||
|   	return true; | ||||
| } | ||||
| 
 | ||||
| #ifndef BOOST_LITTLE_ENDIAN | ||||
| #if BOOST_ENDIAN_BIG_BYTE | ||||
| // Swap a buffer of 32bit data from little endian to big endian and vice versa.
 | ||||
| void stl_internal_reverse_quads(char *buf, size_t cnt) | ||||
| { | ||||
|   for (size_t i = 0; i < cnt; i += 4) { | ||||
|     std::swap(buf[i], buf[i+3]); | ||||
|     std::swap(buf[i+1], buf[i+2]); | ||||
|   } | ||||
| 	for (size_t i = 0; i < cnt; i += 4) { | ||||
| 		std::swap(buf[i], buf[i+3]); | ||||
| 		std::swap(buf[i+1], buf[i+2]); | ||||
| 	} | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| void | ||||
| stl_write_binary(stl_file *stl, const char *file, const char *label) { | ||||
|   FILE      *fp; | ||||
|   int       i; | ||||
|   char      *error_msg; | ||||
| bool stl_write_binary(stl_file *stl, const char *file, const char *label) | ||||
| { | ||||
| 	FILE *fp = boost::nowide::fopen(file, "wb"); | ||||
| 	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_binary: Couldn't open " << file << " for writing"; | ||||
|     	return false; | ||||
|   	} | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 	fprintf(fp, "%s", label); | ||||
| 	for (size_t i = strlen(label); i < LABEL_SIZE; ++ i) | ||||
| 		putc(0, fp); | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "wb"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   fprintf(fp, "%s", label); | ||||
|   for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); | ||||
| 
 | ||||
|   fseek(fp, LABEL_SIZE, SEEK_SET); | ||||
| #ifdef BOOST_LITTLE_ENDIAN | ||||
|   fwrite(&stl->stats.number_of_facets, 4, 1, fp); | ||||
|   for (i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp); | ||||
| #else /* BOOST_LITTLE_ENDIAN */ | ||||
|   char buffer[50]; | ||||
|   // Convert the number of facets to little endian.
 | ||||
|   memcpy(buffer, &stl->stats.number_of_facets, 4); | ||||
|   stl_internal_reverse_quads(buffer, 4); | ||||
|   fwrite(buffer, 4, 1, fp); | ||||
|   for (i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     memcpy(buffer, stl->facet_start + i, 50); | ||||
|     // Convert to little endian.
 | ||||
|     stl_internal_reverse_quads(buffer, 48); | ||||
|     fwrite(buffer, SIZEOF_STL_FACET, 1, fp); | ||||
|   } | ||||
| #endif /* BOOST_LITTLE_ENDIAN */ | ||||
|   fclose(fp); | ||||
| #if !defined(SEEK_SET) | ||||
| 	#define SEEK_SET 0 | ||||
| #endif | ||||
| 	fseek(fp, LABEL_SIZE, SEEK_SET); | ||||
| #if BOOST_ENDIAN_LITTLE_BYTE | ||||
| 	fwrite(&stl->stats.number_of_facets, 4, 1, fp); | ||||
| 	for (const stl_facet &facet : stl->facet_start) | ||||
| 	  	fwrite(&facet, SIZEOF_STL_FACET, 1, fp); | ||||
| #else /* BOOST_ENDIAN_LITTLE_BYTE */ | ||||
| 	char buffer[50]; | ||||
| 	// Convert the number of facets to little endian.
 | ||||
| 	memcpy(buffer, &stl->stats.number_of_facets, 4); | ||||
| 	stl_internal_reverse_quads(buffer, 4); | ||||
| 	fwrite(buffer, 4, 1, fp); | ||||
| 	for (i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		memcpy(buffer, stl->facet_start + i, 50); | ||||
| 		// Convert to little endian.
 | ||||
| 		stl_internal_reverse_quads(buffer, 48); | ||||
| 		fwrite(buffer, SIZEOF_STL_FACET, 1, fp); | ||||
| 	} | ||||
| #endif /* BOOST_ENDIAN_LITTLE_BYTE */ | ||||
| 	fclose(fp); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_vertex(stl_file *stl, int facet, int vertex) { | ||||
|   if (stl->error) return; | ||||
|   printf("  vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, | ||||
| void stl_write_vertex(stl_file *stl, int facet, int vertex) | ||||
| { | ||||
|   	printf("  vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, | ||||
|          stl->facet_start[facet].vertex[vertex](0), | ||||
|          stl->facet_start[facet].vertex[vertex](1), | ||||
|          stl->facet_start[facet].vertex[vertex](2)); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_facet(stl_file *stl, char *label, int facet) { | ||||
|   if (stl->error) return; | ||||
|   printf("facet (%d)/ %s\n", facet, label); | ||||
|   stl_write_vertex(stl, facet, 0); | ||||
|   stl_write_vertex(stl, facet, 1); | ||||
|   stl_write_vertex(stl, facet, 2); | ||||
| void stl_write_facet(stl_file *stl, char *label, int facet) | ||||
| { | ||||
| 	printf("facet (%d)/ %s\n", facet, label); | ||||
| 	stl_write_vertex(stl, facet, 0); | ||||
| 	stl_write_vertex(stl, facet, 1); | ||||
| 	stl_write_vertex(stl, facet, 2); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge) { | ||||
|   if (stl->error) return; | ||||
|   printf("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label); | ||||
|   if(edge.which_edge < 3) { | ||||
|     stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); | ||||
|     stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); | ||||
|   } else { | ||||
|     stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); | ||||
|     stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); | ||||
|   } | ||||
| void stl_write_neighbor(stl_file *stl, int facet) | ||||
| { | ||||
| 	printf("Neighbors %d: %d, %d, %d ;  %d, %d, %d\n", facet, | ||||
| 		stl->neighbors_start[facet].neighbor[0], | ||||
| 		stl->neighbors_start[facet].neighbor[1], | ||||
| 		stl->neighbors_start[facet].neighbor[2], | ||||
| 		stl->neighbors_start[facet].which_vertex_not[0], | ||||
| 		stl->neighbors_start[facet].which_vertex_not[1], | ||||
| 		stl->neighbors_start[facet].which_vertex_not[2]); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_neighbor(stl_file *stl, int facet) { | ||||
|   if (stl->error) return; | ||||
|   printf("Neighbors %d: %d, %d, %d ;  %d, %d, %d\n", facet, | ||||
|          stl->neighbors_start[facet].neighbor[0], | ||||
|          stl->neighbors_start[facet].neighbor[1], | ||||
|          stl->neighbors_start[facet].neighbor[2], | ||||
|          stl->neighbors_start[facet].which_vertex_not[0], | ||||
|          stl->neighbors_start[facet].which_vertex_not[1], | ||||
|          stl->neighbors_start[facet].which_vertex_not[2]); | ||||
| } | ||||
| bool stl_write_quad_object(stl_file *stl, char *file) | ||||
| { | ||||
| 	stl_vertex connect_color = stl_vertex::Zero(); | ||||
| 	stl_vertex uncon_1_color = stl_vertex::Zero(); | ||||
| 	stl_vertex uncon_2_color = stl_vertex::Zero(); | ||||
| 	stl_vertex uncon_3_color = stl_vertex::Zero(); | ||||
| 	stl_vertex color; | ||||
| 
 | ||||
| void | ||||
| stl_write_quad_object(stl_file *stl, char *file) { | ||||
|   FILE      *fp; | ||||
|   int       i; | ||||
|   int       j; | ||||
|   char      *error_msg; | ||||
|   stl_vertex connect_color = stl_vertex::Zero(); | ||||
|   stl_vertex uncon_1_color = stl_vertex::Zero(); | ||||
|   stl_vertex uncon_2_color = stl_vertex::Zero(); | ||||
|   stl_vertex uncon_3_color = stl_vertex::Zero(); | ||||
|   stl_vertex color; | ||||
| 	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
| 	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "w"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   fprintf(fp, "CQUAD\n"); | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     j = ((stl->neighbors_start[i].neighbor[0] == -1) + | ||||
|          (stl->neighbors_start[i].neighbor[1] == -1) + | ||||
|          (stl->neighbors_start[i].neighbor[2] == -1)); | ||||
|     if(j == 0) { | ||||
|       color = connect_color; | ||||
|     } else if(j == 1) { | ||||
|       color = uncon_1_color; | ||||
|     } else if(j == 2) { | ||||
|       color = uncon_2_color; | ||||
|     } else { | ||||
|       color = uncon_3_color; | ||||
|     } | ||||
|     fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", | ||||
|             stl->facet_start[i].vertex[0](0), | ||||
|             stl->facet_start[i].vertex[0](1), | ||||
|             stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); | ||||
|     fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", | ||||
|             stl->facet_start[i].vertex[1](0), | ||||
|             stl->facet_start[i].vertex[1](1), | ||||
|             stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); | ||||
|     fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", | ||||
|             stl->facet_start[i].vertex[2](0), | ||||
|             stl->facet_start[i].vertex[2](1), | ||||
|             stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); | ||||
|     fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", | ||||
|             stl->facet_start[i].vertex[2](0), | ||||
|             stl->facet_start[i].vertex[2](1), | ||||
|             stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); | ||||
|   	fprintf(fp, "CQUAD\n"); | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|   		switch (stl->neighbors_start[i].num_neighbors_missing()) { | ||||
|   		case 0: color = connect_color; break; | ||||
|     	case 1: color = uncon_1_color; break; | ||||
|     	case 2: color = uncon_2_color; break; | ||||
|     	default: color = uncon_3_color; | ||||
| 	    } | ||||
| 	    fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); | ||||
|     	fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); | ||||
|     	fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); | ||||
|     	fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); | ||||
|   } | ||||
|   fclose(fp); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_write_dxf(stl_file *stl, const char *file, char *label) { | ||||
|   int       i; | ||||
|   FILE      *fp; | ||||
|   char      *error_msg; | ||||
| bool stl_write_dxf(stl_file *stl, const char *file, char *label)  | ||||
| { | ||||
| 	FILE *fp = boost::nowide::fopen(file, "w"); | ||||
| 	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; | ||||
|     	return false; | ||||
|   	} | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 	fprintf(fp, "999\n%s\n", label); | ||||
| 	fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); | ||||
| 	fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\
 | ||||
| 	0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); | ||||
| 	fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); | ||||
| 
 | ||||
|   /* Open the file */ | ||||
|   fp = boost::nowide::fopen(file, "w"); | ||||
|   if(fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
| 	fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); | ||||
| 
 | ||||
|   fprintf(fp, "999\n%s\n", label); | ||||
|   fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); | ||||
|   fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\
 | ||||
| 0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); | ||||
|   fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); | ||||
| 	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		fprintf(fp, "0\n3DFACE\n8\n0\n"); | ||||
| 		fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2)); | ||||
| 		fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2)); | ||||
| 		fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); | ||||
| 		fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); | ||||
| 	} | ||||
| 
 | ||||
|   fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     fprintf(fp, "0\n3DFACE\n8\n0\n"); | ||||
|     fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", | ||||
|             stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), | ||||
|             stl->facet_start[i].vertex[0](2)); | ||||
|     fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", | ||||
|             stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), | ||||
|             stl->facet_start[i].vertex[1](2)); | ||||
|     fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", | ||||
|             stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), | ||||
|             stl->facet_start[i].vertex[2](2)); | ||||
|     fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", | ||||
|             stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), | ||||
|             stl->facet_start[i].vertex[2](2)); | ||||
|   } | ||||
| 
 | ||||
|   fprintf(fp, "0\nENDSEC\n0\nEOF\n"); | ||||
| 
 | ||||
|   fclose(fp); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_clear_error(stl_file *stl) { | ||||
|   stl->error = 0; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_exit_on_error(stl_file *stl) { | ||||
|   if (!stl->error) return; | ||||
|   stl->error = 0; | ||||
|   stl_close(stl); | ||||
|   exit(1); | ||||
| } | ||||
| 
 | ||||
| int | ||||
| stl_get_error(stl_file *stl) { | ||||
|   return stl->error; | ||||
|   	fprintf(fp, "0\nENDSEC\n0\nEOF\n"); | ||||
|   	fclose(fp); | ||||
|   	return true; | ||||
| } | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ | |||
| #include <math.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| #include <boost/detail/endian.hpp> | ||||
| 
 | ||||
|  | @ -35,351 +36,236 @@ | |||
| #error "SEEK_SET not defined" | ||||
| #endif | ||||
| 
 | ||||
| void | ||||
| stl_open(stl_file *stl, const char *file) { | ||||
|   stl_initialize(stl); | ||||
|   stl_count_facets(stl, file); | ||||
|   stl_allocate(stl); | ||||
|   stl_read(stl, 0, true); | ||||
|   if (stl->fp != nullptr) { | ||||
| 	  fclose(stl->fp); | ||||
| 	  stl->fp = nullptr; | ||||
|   } | ||||
| static FILE* stl_open_count_facets(stl_file *stl, const char *file)  | ||||
| { | ||||
|   	// Open the file in binary mode first.
 | ||||
|   	FILE *fp = boost::nowide::fopen(file, "rb"); | ||||
|   	if (fp == nullptr) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading"; | ||||
|     	return nullptr; | ||||
|   	} | ||||
|   	// Find size of file.
 | ||||
|   	fseek(fp, 0, SEEK_END); | ||||
|   	long file_size = ftell(fp); | ||||
| 
 | ||||
|   	// Check for binary or ASCII file.
 | ||||
|   	fseek(fp, HEADER_SIZE, SEEK_SET); | ||||
| 	unsigned char chtest[128]; | ||||
|   	if (! fread(chtest, sizeof(chtest), 1, fp)) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The input is an empty file: " << file; | ||||
|     	fclose(fp); | ||||
|     	return nullptr; | ||||
|   	} | ||||
|   	stl->stats.type = ascii; | ||||
|   	for (size_t s = 0; s < sizeof(chtest); s++) { | ||||
|     	if (chtest[s] > 127) { | ||||
|       		stl->stats.type = binary; | ||||
|       		break; | ||||
|     	} | ||||
|   	} | ||||
|   	rewind(fp); | ||||
| 
 | ||||
|   	uint32_t num_facets = 0; | ||||
| 
 | ||||
|   	// Get the header and the number of facets in the .STL file.
 | ||||
|   	// If the .STL file is binary, then do the following:
 | ||||
|   	if (stl->stats.type == binary) { | ||||
|     	// Test if the STL file has the right size.
 | ||||
|     	if (((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The file " << file << " has the wrong size."; | ||||
|       		fclose(fp); | ||||
|       		return nullptr; | ||||
|     	} | ||||
|     	num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; | ||||
| 
 | ||||
|     	// Read the header.
 | ||||
|     	if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79) | ||||
|       		stl->stats.header[80] = '\0'; | ||||
| 
 | ||||
|     	// Read the int following the header.  This should contain # of facets.
 | ||||
| 	  	uint32_t header_num_facets; | ||||
|     	bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0; | ||||
| #ifndef BOOST_LITTLE_ENDIAN | ||||
|     	// Convert from little endian to big endian.
 | ||||
|     	stl_internal_reverse_quads((char*)&header_num_facets, 4); | ||||
| #endif /* BOOST_LITTLE_ENDIAN */ | ||||
|     	if (! header_num_faces_read || num_facets != header_num_facets) | ||||
| 			BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file; | ||||
|   	} | ||||
|   	// Otherwise, if the .STL file is ASCII, then do the following:
 | ||||
|   	else | ||||
|   	{ | ||||
|     	// Reopen the file in text mode (for getting correct newlines on Windows)
 | ||||
|     	// fix to silence a warning about unused return value.
 | ||||
|     	// obviously if it fails we have problems....
 | ||||
|     	fp = boost::nowide::freopen(file, "r", fp); | ||||
| 
 | ||||
| 		// do another null check to be safe
 | ||||
|     	if (fp == nullptr) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading"; | ||||
|       		fclose(fp); | ||||
|       		return nullptr; | ||||
|     	} | ||||
|      | ||||
|     	// Find the number of facets.
 | ||||
| 		char linebuf[100]; | ||||
| 		int num_lines = 1; | ||||
| 		while (fgets(linebuf, 100, fp) != nullptr) { | ||||
| 		    // Don't count short lines.
 | ||||
| 		    if (strlen(linebuf) <= 4) | ||||
| 		    	continue; | ||||
| 		    // Skip solid/endsolid lines as broken STL file generators may put several of them.
 | ||||
| 		    if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) | ||||
| 		    	continue; | ||||
| 		    ++ num_lines; | ||||
| 		} | ||||
| 
 | ||||
|     	rewind(fp); | ||||
|      | ||||
|     	// Get the header.
 | ||||
| 		int i = 0; | ||||
|     	for (; i < 80 && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ; | ||||
|     	stl->stats.header[i] = '\0'; // Lose the '\n'
 | ||||
|     	stl->stats.header[80] = '\0'; | ||||
| 
 | ||||
|     	num_facets = num_lines / ASCII_LINES_PER_FACET; | ||||
|   	} | ||||
| 
 | ||||
|   	stl->stats.number_of_facets += num_facets; | ||||
|   	stl->stats.original_num_facets = stl->stats.number_of_facets; | ||||
|   	return fp; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_initialize(stl_file *stl) { | ||||
|   memset(stl, 0, sizeof(stl_file)); | ||||
|   stl->stats.volume = -1.0; | ||||
| /* Reads the contents of the file pointed to by fp into the stl structure,
 | ||||
|    starting at facet first_facet.  The second argument says if it's our first | ||||
|    time running this for the stl and therefore we should reset our max and min stats. */ | ||||
| static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) | ||||
| { | ||||
| 	if (stl->stats.type == binary) | ||||
|     	fseek(fp, HEADER_SIZE, SEEK_SET); | ||||
|   	else | ||||
|     	rewind(fp); | ||||
| 
 | ||||
|   	char normal_buf[3][32]; | ||||
|   	for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++ i) { | ||||
|   	  	stl_facet facet; | ||||
| 
 | ||||
|     	if (stl->stats.type == binary) { | ||||
|       		// Read a single facet from a binary .STL file. We assume little-endian architecture!
 | ||||
|       		if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET) | ||||
|       			return false; | ||||
| #ifndef BOOST_LITTLE_ENDIAN | ||||
|       		// Convert the loaded little endian data to big endian.
 | ||||
|       		stl_internal_reverse_quads((char*)&facet, 48); | ||||
| #endif /* BOOST_LITTLE_ENDIAN */ | ||||
|     	} else { | ||||
| 			// Read a single facet from an ASCII .STL file
 | ||||
| 			// skip solid/endsolid
 | ||||
| 			// (in this order, otherwise it won't work when they are paired in the middle of a file)
 | ||||
| 			fscanf(fp, "endsolid%*[^\n]\n"); | ||||
| 			fscanf(fp, "solid%*[^\n]\n");  // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
 | ||||
| 			// Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs.
 | ||||
| 			int res_normal     = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); | ||||
| 			assert(res_normal == 3); | ||||
| 			int res_outer_loop = fscanf(fp, " outer loop"); | ||||
| 			assert(res_outer_loop == 0); | ||||
| 			int res_vertex1    = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); | ||||
| 			assert(res_vertex1 == 3); | ||||
| 			int res_vertex2    = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); | ||||
| 			assert(res_vertex2 == 3); | ||||
| 			int res_vertex3    = fscanf(fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); | ||||
| 			assert(res_vertex3 == 3); | ||||
| 			int res_endloop    = fscanf(fp, " endloop"); | ||||
| 			assert(res_endloop == 0); | ||||
| 			// There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines.
 | ||||
| 			int res_endfacet   = fscanf(fp, " endfacet "); | ||||
| 			if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { | ||||
| 				BOOST_LOG_TRIVIAL(error) << "Something is syntactically very wrong with this ASCII STL! "; | ||||
| 				return false; | ||||
| 			} | ||||
| 
 | ||||
| 			// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
 | ||||
| 			if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || | ||||
| 			    sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || | ||||
| 			    sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { | ||||
| 			    // Normal was mangled. Maybe denormals or "not a number" were stored?
 | ||||
| 			  	// Just reset the normal and silently ignore it.
 | ||||
| 			  	memset(&facet.normal, 0, sizeof(facet.normal)); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| #if 0 | ||||
| 		// Report close to zero vertex coordinates. Due to the nature of the floating point numbers,
 | ||||
| 		// close to zero values may be represented with singificantly higher precision than the rest of the vertices.
 | ||||
| 		// It may be worth to round these numbers to zero during loading to reduce the number of errors reported
 | ||||
| 		// during the STL import.
 | ||||
| 		for (size_t j = 0; j < 3; ++ j) { | ||||
| 		if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) | ||||
| 		    printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); | ||||
| 		if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) | ||||
| 		    printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); | ||||
| 		if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) | ||||
| 		    printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); | ||||
| 		} | ||||
| #endif | ||||
| 
 | ||||
| 		// Write the facet into memory.
 | ||||
| 		stl->facet_start[i] = facet; | ||||
| 		stl_facet_stats(stl, facet, first); | ||||
|   	} | ||||
|    | ||||
|   	stl->stats.size = stl->stats.max - stl->stats.min; | ||||
|   	stl->stats.bounding_diameter = stl->stats.size.norm(); | ||||
|   	return true; | ||||
| } | ||||
| 
 | ||||
| bool stl_open(stl_file *stl, const char *file) | ||||
| { | ||||
| 	stl->clear(); | ||||
| 	FILE *fp = stl_open_count_facets(stl, file); | ||||
| 	if (fp == nullptr) | ||||
| 		return false; | ||||
| 	stl_allocate(stl); | ||||
| 	bool result = stl_read(stl, fp, 0, true); | ||||
|   	fclose(fp); | ||||
|   	return result; | ||||
| } | ||||
| 
 | ||||
| #ifndef BOOST_LITTLE_ENDIAN | ||||
| extern void stl_internal_reverse_quads(char *buf, size_t cnt); | ||||
| #endif /* BOOST_LITTLE_ENDIAN */ | ||||
| 
 | ||||
| void | ||||
| stl_count_facets(stl_file *stl, const char *file) { | ||||
|   long           file_size; | ||||
|   uint32_t       header_num_facets; | ||||
|   uint32_t       num_facets; | ||||
|   int            i; | ||||
|   size_t         s; | ||||
|   unsigned char  chtest[128]; | ||||
|   int            num_lines = 1; | ||||
|   char           *error_msg; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Open the file in binary mode first */ | ||||
|   stl->fp = boost::nowide::fopen(file, "rb"); | ||||
|   if(stl->fp == NULL) { | ||||
|     error_msg = (char*) | ||||
|                 malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|     sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", | ||||
|             file); | ||||
|     perror(error_msg); | ||||
|     free(error_msg); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
|   /* Find size of file */ | ||||
|   fseek(stl->fp, 0, SEEK_END); | ||||
|   file_size = ftell(stl->fp); | ||||
| 
 | ||||
|   /* Check for binary or ASCII file */ | ||||
|   fseek(stl->fp, HEADER_SIZE, SEEK_SET); | ||||
|   if (!fread(chtest, sizeof(chtest), 1, stl->fp)) { | ||||
|     perror("The input is an empty file"); | ||||
|     stl->error = 1; | ||||
|     return; | ||||
|   } | ||||
|   stl->stats.type = ascii; | ||||
|   for(s = 0; s < sizeof(chtest); s++) { | ||||
|     if(chtest[s] > 127) { | ||||
|       stl->stats.type = binary; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   rewind(stl->fp); | ||||
| 
 | ||||
|   /* Get the header and the number of facets in the .STL file */ | ||||
|   /* If the .STL file is binary, then do the following */ | ||||
|   if(stl->stats.type == binary) { | ||||
|     /* Test if the STL file has the right size  */ | ||||
|     if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) | ||||
|         || (file_size < STL_MIN_FILE_SIZE)) { | ||||
|       fprintf(stderr, "The file %s has the wrong size.\n", file); | ||||
|       stl->error = 1; | ||||
|       return; | ||||
|     } | ||||
|     num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; | ||||
| 
 | ||||
|     /* Read the header */ | ||||
|     if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) { | ||||
|       stl->stats.header[80] = '\0'; | ||||
|     } | ||||
| 
 | ||||
|     /* Read the int following the header.  This should contain # of facets */ | ||||
|     bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0; | ||||
| #ifndef BOOST_LITTLE_ENDIAN | ||||
|     // Convert from little endian to big endian.
 | ||||
|     stl_internal_reverse_quads((char*)&header_num_facets, 4); | ||||
| #endif /* BOOST_LITTLE_ENDIAN */ | ||||
|     if (! header_num_faces_read || num_facets != header_num_facets) { | ||||
|       fprintf(stderr, | ||||
|               "Warning: File size doesn't match number of facets in the header\n"); | ||||
|     } | ||||
|   } | ||||
|   /* Otherwise, if the .STL file is ASCII, then do the following */ | ||||
|   else { | ||||
|     /* Reopen the file in text mode (for getting correct newlines on Windows) */ | ||||
|     // fix to silence a warning about unused return value.
 | ||||
|     // obviously if it fails we have problems....
 | ||||
|     stl->fp = boost::nowide::freopen(file, "r", stl->fp); | ||||
| 
 | ||||
|     // do another null check to be safe
 | ||||
|     if(stl->fp == NULL) { | ||||
|       error_msg = (char*) | ||||
|         malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ | ||||
|       sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", | ||||
|           file); | ||||
|       perror(error_msg); | ||||
|       free(error_msg); | ||||
|       stl->error = 1; | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|     /* Find the number of facets */ | ||||
|     char linebuf[100]; | ||||
|     while (fgets(linebuf, 100, stl->fp) != NULL) { | ||||
|         /* don't count short lines */ | ||||
|         if (strlen(linebuf) <= 4) continue; | ||||
|          | ||||
|         /* skip solid/endsolid lines as broken STL file generators may put several of them */ | ||||
|         if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; | ||||
|          | ||||
|         ++num_lines; | ||||
|     } | ||||
|      | ||||
|     rewind(stl->fp); | ||||
|      | ||||
|     /* Get the header */ | ||||
|     for(i = 0; | ||||
|         (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); | ||||
|     stl->stats.header[i] = '\0'; /* Lose the '\n' */ | ||||
|     stl->stats.header[80] = '\0'; | ||||
| 
 | ||||
|     num_facets = num_lines / ASCII_LINES_PER_FACET; | ||||
|   } | ||||
|   stl->stats.number_of_facets += num_facets; | ||||
|   stl->stats.original_num_facets = stl->stats.number_of_facets; | ||||
| void stl_allocate(stl_file *stl)  | ||||
| { | ||||
|   	//  Allocate memory for the entire .STL file.
 | ||||
|   	stl->facet_start.assign(stl->stats.number_of_facets, stl_facet()); | ||||
|   	// Allocate memory for the neighbors list.
 | ||||
|   	stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors()); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_allocate(stl_file *stl) { | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /*  Allocate memory for the entire .STL file */ | ||||
|   stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets, | ||||
|                                         sizeof(stl_facet)); | ||||
|   if(stl->facet_start == NULL) perror("stl_initialize"); | ||||
|   stl->stats.facets_malloced = stl->stats.number_of_facets; | ||||
| 
 | ||||
|   /* Allocate memory for the neighbors list */ | ||||
|   stl->neighbors_start = (stl_neighbors*) | ||||
|                          calloc(stl->stats.number_of_facets, sizeof(stl_neighbors)); | ||||
|   if(stl->facet_start == NULL) perror("stl_initialize"); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_open_merge(stl_file *stl, char *file_to_merge) { | ||||
|   int num_facets_so_far; | ||||
|   stl_type origStlType; | ||||
|   FILE *origFp; | ||||
|   stl_file stl_to_merge; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   /* Record how many facets we have so far from the first file.  We will start putting
 | ||||
|      facets in the next position.  Since we're 0-indexed, it'l be the same position. */ | ||||
|   num_facets_so_far = stl->stats.number_of_facets; | ||||
| 
 | ||||
|   /* Record the file type we started with: */ | ||||
|   origStlType=stl->stats.type; | ||||
|   /* Record the file pointer too: */ | ||||
|   origFp=stl->fp; | ||||
| 
 | ||||
|   /* Initialize the sturucture with zero stats, header info and sizes: */ | ||||
|   stl_initialize(&stl_to_merge); | ||||
|   stl_count_facets(&stl_to_merge, file_to_merge); | ||||
| 
 | ||||
|   /* Copy what we need to into stl so that we can read the file_to_merge directly into it
 | ||||
|      using stl_read:  Save the rest of the valuable info: */ | ||||
|   stl->stats.type=stl_to_merge.stats.type; | ||||
|   stl->fp=stl_to_merge.fp; | ||||
| 
 | ||||
|   /* Add the number of facets we already have in stl with what we we found in stl_to_merge but
 | ||||
|      haven't read yet. */ | ||||
|   stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets; | ||||
| 
 | ||||
|   /* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */ | ||||
|   stl_reallocate(stl); | ||||
| 
 | ||||
|   /* Read the file to merge directly into stl, adding it to what we have already.
 | ||||
|      Start at num_facets_so_far, the index to the first unused facet.  Also say | ||||
|      that this isn't our first time so we should augment stats like min and max | ||||
|      instead of erasing them. */ | ||||
|   stl_read(stl, num_facets_so_far, false); | ||||
| 
 | ||||
|   /* Restore the stl information we overwrote (for stl_read) so that it still accurately
 | ||||
|      reflects the subject part: */ | ||||
|   stl->stats.type=origStlType; | ||||
|   stl->fp=origFp; | ||||
| } | ||||
| 
 | ||||
| extern void | ||||
| stl_reallocate(stl_file *stl) { | ||||
|   if (stl->error) return; | ||||
|   /*  Reallocate more memory for the .STL file(s) */ | ||||
|   stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets * | ||||
|                                          sizeof(stl_facet)); | ||||
|   if(stl->facet_start == NULL) perror("stl_initialize"); | ||||
|   stl->stats.facets_malloced = stl->stats.number_of_facets; | ||||
| 
 | ||||
|   /* Reallocate more memory for the neighbors list */ | ||||
|   stl->neighbors_start = (stl_neighbors*) | ||||
|                          realloc(stl->neighbors_start, stl->stats.number_of_facets * | ||||
|                                  sizeof(stl_neighbors)); | ||||
|   if(stl->facet_start == NULL) perror("stl_initialize"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Reads the contents of the file pointed to by stl->fp into the stl structure,
 | ||||
|    starting at facet first_facet.  The second argument says if it's our first | ||||
|    time running this for the stl and therefore we should reset our max and min stats. */ | ||||
| void stl_read(stl_file *stl, int first_facet, bool first) { | ||||
|   stl_facet facet; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   if(stl->stats.type == binary) { | ||||
|     fseek(stl->fp, HEADER_SIZE, SEEK_SET); | ||||
|   } else { | ||||
|     rewind(stl->fp); | ||||
|   } | ||||
| 
 | ||||
|   char normal_buf[3][32]; | ||||
|   for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) { | ||||
|     if(stl->stats.type == binary) | ||||
|       /* Read a single facet from a binary .STL file */ | ||||
|     { | ||||
|       /* we assume little-endian architecture! */ | ||||
|       if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) { | ||||
|         stl->error = 1; | ||||
|         return; | ||||
|       } | ||||
| #ifndef BOOST_LITTLE_ENDIAN | ||||
|       // Convert the loaded little endian data to big endian.
 | ||||
|       stl_internal_reverse_quads((char*)&facet, 48); | ||||
| #endif /* BOOST_LITTLE_ENDIAN */ | ||||
|     } else | ||||
|       /* Read a single facet from an ASCII .STL file */ | ||||
|     { | ||||
|       // skip solid/endsolid
 | ||||
|       // (in this order, otherwise it won't work when they are paired in the middle of a file)
 | ||||
|       fscanf(stl->fp, "endsolid%*[^\n]\n"); | ||||
|       fscanf(stl->fp, "solid%*[^\n]\n");  // name might contain spaces so %*s doesn't work and it also can be empty (just "solid")
 | ||||
|       // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs.
 | ||||
|       int res_normal     = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); | ||||
|       assert(res_normal == 3); | ||||
|       int res_outer_loop = fscanf(stl->fp, " outer loop"); | ||||
|       assert(res_outer_loop == 0); | ||||
|       int res_vertex1    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); | ||||
|       assert(res_vertex1 == 3); | ||||
|       int res_vertex2    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); | ||||
|       assert(res_vertex2 == 3); | ||||
|       int res_vertex3    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); | ||||
|       assert(res_vertex3 == 3); | ||||
|       int res_endloop    = fscanf(stl->fp, " endloop"); | ||||
|       assert(res_endloop == 0); | ||||
|       // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines.
 | ||||
|       int res_endfacet   = fscanf(stl->fp, " endfacet "); | ||||
|       if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { | ||||
|         perror("Something is syntactically very wrong with this ASCII STL!"); | ||||
|         stl->error = 1; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
 | ||||
| 	  if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || | ||||
| 		  sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || | ||||
| 		  sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { | ||||
| 		  // Normal was mangled. Maybe denormals or "not a number" were stored?
 | ||||
| 		  // Just reset the normal and silently ignore it.
 | ||||
| 		  memset(&facet.normal, 0, sizeof(facet.normal)); | ||||
| 	  } | ||||
|     } | ||||
| 
 | ||||
| #if 0 | ||||
|       // Report close to zero vertex coordinates. Due to the nature of the floating point numbers,
 | ||||
|       // close to zero values may be represented with singificantly higher precision than the rest of the vertices.
 | ||||
|       // It may be worth to round these numbers to zero during loading to reduce the number of errors reported
 | ||||
|       // during the STL import.
 | ||||
|       for (size_t j = 0; j < 3; ++ j) { | ||||
|         if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) | ||||
|             printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); | ||||
|         if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) | ||||
|             printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); | ||||
|         if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) | ||||
|             printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); | ||||
|       } | ||||
| #endif | ||||
| 
 | ||||
|     /* Write the facet into memory. */ | ||||
|     stl->facet_start[i] = facet; | ||||
|     stl_facet_stats(stl, facet, first); | ||||
|   } | ||||
|   stl->stats.size = stl->stats.max - stl->stats.min; | ||||
|   stl->stats.bounding_diameter = stl->stats.size.norm(); | ||||
| void stl_reallocate(stl_file *stl)  | ||||
| { | ||||
| 	stl->facet_start.resize(stl->stats.number_of_facets); | ||||
| 	stl->neighbors_start.resize(stl->stats.number_of_facets); | ||||
| } | ||||
| 
 | ||||
| void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 	// While we are going through all of the facets, let's find the
 | ||||
| 	// maximum and minimum values for x, y, and z
 | ||||
| 
 | ||||
|   // While we are going through all of the facets, let's find the
 | ||||
|   // maximum and minimum values for x, y, and z
 | ||||
| 	if (first) { | ||||
| 		// Initialize the max and min values the first time through
 | ||||
| 		stl->stats.min = facet.vertex[0]; | ||||
| 		stl->stats.max = facet.vertex[0]; | ||||
| 		stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); | ||||
| 		stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); | ||||
| 		first = false; | ||||
| 	} | ||||
| 
 | ||||
|   if (first) { | ||||
| 	// Initialize the max and min values the first time through
 | ||||
|     stl->stats.min = facet.vertex[0]; | ||||
|     stl->stats.max = facet.vertex[0]; | ||||
|     stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); | ||||
|     stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); | ||||
|     first = false; | ||||
|   } | ||||
| 
 | ||||
|   // Now find the max and min values.
 | ||||
|   for (size_t i = 0; i < 3; ++ i) { | ||||
|   	stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); | ||||
|   	stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void stl_close(stl_file *stl) | ||||
| { | ||||
| 	assert(stl->fp == nullptr); | ||||
| 	assert(stl->heads == nullptr); | ||||
| 	assert(stl->tail == nullptr); | ||||
| 
 | ||||
| 	if (stl->facet_start != NULL) | ||||
| 		free(stl->facet_start); | ||||
| 	if (stl->neighbors_start != NULL) | ||||
| 		free(stl->neighbors_start); | ||||
| 	if (stl->v_indices != NULL) | ||||
| 		free(stl->v_indices); | ||||
| 	if (stl->v_shared != NULL) | ||||
| 		free(stl->v_shared); | ||||
| 	memset(stl, 0, sizeof(stl_file)); | ||||
| 	// Now find the max and min values.
 | ||||
| 	for (size_t i = 0; i < 3; ++ i) { | ||||
| 		stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); | ||||
| 		stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -25,435 +25,375 @@ | |||
| #include <string.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| #include "stl.h" | ||||
| 
 | ||||
| static void stl_rotate(float *x, float *y, const double c, const double s); | ||||
| static float get_area(stl_facet *facet); | ||||
| static float get_volume(stl_file *stl); | ||||
| void stl_verify_neighbors(stl_file *stl) | ||||
| { | ||||
| 	stl->stats.backwards_edges = 0; | ||||
| 
 | ||||
| 
 | ||||
| void | ||||
| stl_verify_neighbors(stl_file *stl) { | ||||
|   int i; | ||||
|   int j; | ||||
|   stl_edge edge_a; | ||||
|   stl_edge edge_b; | ||||
|   int neighbor; | ||||
|   int vnot; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   stl->stats.backwards_edges = 0; | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for(j = 0; j < 3; j++) { | ||||
|       edge_a.p1 = stl->facet_start[i].vertex[j]; | ||||
|       edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; | ||||
|       neighbor = stl->neighbors_start[i].neighbor[j]; | ||||
|       vnot = stl->neighbors_start[i].which_vertex_not[j]; | ||||
| 
 | ||||
|       if(neighbor == -1) | ||||
|         continue;		/* this edge has no neighbor... Continue. */ | ||||
|       if(vnot < 3) { | ||||
|         edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; | ||||
|         edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; | ||||
|       } else { | ||||
|         stl->stats.backwards_edges += 1; | ||||
|         edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; | ||||
|         edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; | ||||
|       } | ||||
|       if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { | ||||
|         /* These edges should match but they don't.  Print results. */ | ||||
|         printf("edge %d of facet %d doesn't match edge %d of facet %d\n", | ||||
|                j, i, vnot + 1, neighbor); | ||||
|         stl_write_facet(stl, (char*)"first facet", i); | ||||
|         stl_write_facet(stl, (char*)"second facet", neighbor); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		for (int j = 0; j < 3; ++ j) { | ||||
| 			struct stl_edge { | ||||
| 				stl_vertex p1; | ||||
| 				stl_vertex p2; | ||||
| 				int        facet_number; | ||||
| 			}; | ||||
| 			stl_edge edge_a; | ||||
| 			edge_a.p1 = stl->facet_start[i].vertex[j]; | ||||
| 			edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; | ||||
| 			int neighbor = stl->neighbors_start[i].neighbor[j]; | ||||
| 			if (neighbor == -1) | ||||
| 				continue; // this edge has no neighbor... Continue.
 | ||||
| 			int vnot = stl->neighbors_start[i].which_vertex_not[j]; | ||||
| 			stl_edge edge_b; | ||||
| 			if (vnot < 3) { | ||||
| 				edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; | ||||
| 				edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; | ||||
| 			} else { | ||||
| 				stl->stats.backwards_edges += 1; | ||||
| 				edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; | ||||
| 				edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; | ||||
| 			} | ||||
| 			if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { | ||||
| 				// These edges should match but they don't.  Print results.
 | ||||
| 				BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor; | ||||
| 				stl_write_facet(stl, (char*)"first facet", i); | ||||
| 				stl_write_facet(stl, (char*)"second facet", neighbor); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void stl_translate(stl_file *stl, float x, float y, float z) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 
 | ||||
|   stl_vertex new_min(x, y, z); | ||||
|   stl_vertex shift = new_min - stl->stats.min; | ||||
|   for (int i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     for (int j = 0; j < 3; ++ j) | ||||
|       stl->facet_start[i].vertex[j] += shift; | ||||
|   stl->stats.min = new_min; | ||||
|   stl->stats.max += shift; | ||||
|   stl_invalidate_shared_vertices(stl); | ||||
| 	stl_vertex new_min(x, y, z); | ||||
| 	stl_vertex shift = new_min - stl->stats.min; | ||||
| 	for (int i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
| 		for (int j = 0; j < 3; ++ j) | ||||
| 	  		stl->facet_start[i].vertex[j] += shift; | ||||
| 	stl->stats.min = new_min; | ||||
| 	stl->stats.max += shift; | ||||
| } | ||||
| 
 | ||||
| /* Translates the stl by x,y,z, relatively from wherever it is currently */ | ||||
| void stl_translate_relative(stl_file *stl, float x, float y, float z) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 
 | ||||
|   stl_vertex shift(x, y, z); | ||||
|   for (int i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     for (int j = 0; j < 3; ++ j) | ||||
|       stl->facet_start[i].vertex[j] += shift; | ||||
|   stl->stats.min += shift; | ||||
|   stl->stats.max += shift; | ||||
|   stl_invalidate_shared_vertices(stl); | ||||
| 	stl_vertex shift(x, y, z); | ||||
| 	for (int i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
| 		for (int j = 0; j < 3; ++ j) | ||||
| 	  		stl->facet_start[i].vertex[j] += shift; | ||||
| 	stl->stats.min += shift; | ||||
| 	stl->stats.max += shift; | ||||
| } | ||||
| 
 | ||||
| void stl_scale_versor(stl_file *stl, const stl_vertex &versor) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 
 | ||||
|   // Scale extents.
 | ||||
|   auto s = versor.array(); | ||||
|   stl->stats.min.array() *= s; | ||||
|   stl->stats.max.array() *= s; | ||||
|   // Scale size.
 | ||||
|   stl->stats.size.array() *= s; | ||||
|   // Scale volume.
 | ||||
|   if (stl->stats.volume > 0.0) | ||||
|     stl->stats.volume *= versor(0) * versor(1) * versor(2); | ||||
|   // Scale the mesh.
 | ||||
|   for (int i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     for (int j = 0; j < 3; ++ j) | ||||
|       stl->facet_start[i].vertex[j].array() *= s; | ||||
|   stl_invalidate_shared_vertices(stl); | ||||
| 	// Scale extents.
 | ||||
| 	auto s = versor.array(); | ||||
| 	stl->stats.min.array() *= s; | ||||
| 	stl->stats.max.array() *= s; | ||||
| 	// Scale size.
 | ||||
| 	stl->stats.size.array() *= s; | ||||
| 	// Scale volume.
 | ||||
| 	if (stl->stats.volume > 0.0) | ||||
| 		stl->stats.volume *= versor(0) * versor(1) * versor(2); | ||||
| 	// Scale the mesh.
 | ||||
| 	for (int i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
| 		for (int j = 0; j < 3; ++ j) | ||||
| 	  		stl->facet_start[i].vertex[j].array() *= s; | ||||
| } | ||||
| 
 | ||||
| static void calculate_normals(stl_file *stl)  | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 
 | ||||
|   stl_normal normal; | ||||
|   for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     stl_calculate_normal(normal, &stl->facet_start[i]); | ||||
|     stl_normalize_vector(normal); | ||||
|     stl->facet_start[i].normal = normal; | ||||
|   } | ||||
| 	stl_normal normal; | ||||
| 	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
| 		stl_calculate_normal(normal, &stl->facet_start[i]); | ||||
| 		stl_normalize_vector(normal); | ||||
| 		stl->facet_start[i].normal = normal; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_rotate_x(stl_file *stl, float angle) { | ||||
|   int i; | ||||
|   int j; | ||||
|   double radian_angle = (angle / 180.0) * M_PI; | ||||
|   double c = cos(radian_angle); | ||||
|   double s = sin(radian_angle); | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for(j = 0; j < 3; j++) { | ||||
|       stl_rotate(&stl->facet_start[i].vertex[j](1), | ||||
|                  &stl->facet_start[i].vertex[j](2), c, s); | ||||
|     } | ||||
|   } | ||||
|   stl_get_size(stl); | ||||
|   calculate_normals(stl); | ||||
| static inline void rotate_point_2d(float &x, float &y, const double c, const double s) | ||||
| { | ||||
| 	double xold = x; | ||||
| 	double yold = y; | ||||
| 	x = float(c * xold - s * yold); | ||||
| 	y = float(s * xold + c * yold); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_rotate_y(stl_file *stl, float angle) { | ||||
|   int i; | ||||
|   int j; | ||||
|   double radian_angle = (angle / 180.0) * M_PI; | ||||
|   double c = cos(radian_angle); | ||||
|   double s = sin(radian_angle); | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for(j = 0; j < 3; j++) { | ||||
|       stl_rotate(&stl->facet_start[i].vertex[j](2), | ||||
|                  &stl->facet_start[i].vertex[j](0), c, s); | ||||
|     } | ||||
|   } | ||||
|   stl_get_size(stl); | ||||
|   calculate_normals(stl); | ||||
| void stl_rotate_x(stl_file *stl, float angle) | ||||
| { | ||||
| 	double radian_angle = (angle / 180.0) * M_PI; | ||||
| 	double c = cos(radian_angle); | ||||
| 	double s = sin(radian_angle); | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     	for (int j = 0; j < 3; ++ j) | ||||
|       		rotate_point_2d(stl->facet_start[i].vertex[j](1), stl->facet_start[i].vertex[j](2), c, s); | ||||
|   	stl_get_size(stl); | ||||
|   	calculate_normals(stl); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| stl_rotate_z(stl_file *stl, float angle) { | ||||
|   int i; | ||||
|   int j; | ||||
|   double radian_angle = (angle / 180.0) * M_PI; | ||||
|   double c = cos(radian_angle); | ||||
|   double s = sin(radian_angle); | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   for(i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for(j = 0; j < 3; j++) { | ||||
|       stl_rotate(&stl->facet_start[i].vertex[j](0), | ||||
|                  &stl->facet_start[i].vertex[j](1), c, s); | ||||
|     } | ||||
|   } | ||||
|   stl_get_size(stl); | ||||
|   calculate_normals(stl); | ||||
| void stl_rotate_y(stl_file *stl, float angle) | ||||
| { | ||||
| 	double radian_angle = (angle / 180.0) * M_PI; | ||||
| 	double c = cos(radian_angle); | ||||
| 	double s = sin(radian_angle); | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     	for (int j = 0; j < 3; ++ j) | ||||
| 			rotate_point_2d(stl->facet_start[i].vertex[j](2), stl->facet_start[i].vertex[j](0), c, s); | ||||
|   	stl_get_size(stl); | ||||
|   	calculate_normals(stl); | ||||
| } | ||||
| 
 | ||||
| void stl_rotate_z(stl_file *stl, float angle) | ||||
| { | ||||
| 	double radian_angle = (angle / 180.0) * M_PI; | ||||
| 	double c = cos(radian_angle); | ||||
| 	double s = sin(radian_angle); | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     	for (int j = 0; j < 3; ++ j) | ||||
|       		rotate_point_2d(stl->facet_start[i].vertex[j](0), stl->facet_start[i].vertex[j](1), c, s); | ||||
|   	stl_get_size(stl); | ||||
|   	calculate_normals(stl); | ||||
| } | ||||
| 
 | ||||
| void its_rotate_x(indexed_triangle_set &its, float angle) | ||||
| { | ||||
| 	double radian_angle = (angle / 180.0) * M_PI; | ||||
| 	double c = cos(radian_angle); | ||||
| 	double s = sin(radian_angle); | ||||
| 	for (stl_vertex &v : its.vertices) | ||||
| 		rotate_point_2d(v(1), v(2), c, s); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| stl_rotate(float *x, float *y, const double c, const double s) { | ||||
|   double xold = *x; | ||||
|   double yold = *y; | ||||
|   *x = float(c * xold - s * yold); | ||||
|   *y = float(s * xold + c * yold); | ||||
| void its_rotate_y(indexed_triangle_set& its, float angle) | ||||
| { | ||||
| 	double radian_angle = (angle / 180.0) * M_PI; | ||||
| 	double c = cos(radian_angle); | ||||
| 	double s = sin(radian_angle); | ||||
| 	for (stl_vertex& v : its.vertices) | ||||
| 		rotate_point_2d(v(2), v(0), c, s); | ||||
| } | ||||
| 
 | ||||
| void its_rotate_z(indexed_triangle_set& its, float angle) | ||||
| { | ||||
| 	double radian_angle = (angle / 180.0) * M_PI; | ||||
| 	double c = cos(radian_angle); | ||||
| 	double s = sin(radian_angle); | ||||
| 	for (stl_vertex& v : its.vertices) | ||||
| 		rotate_point_2d(v(0), v(1), c, s); | ||||
| } | ||||
| 
 | ||||
| void stl_get_size(stl_file *stl) | ||||
| { | ||||
|   if (stl->error || stl->stats.number_of_facets == 0) | ||||
|   	return; | ||||
|   stl->stats.min = stl->facet_start[0].vertex[0]; | ||||
|   stl->stats.max = stl->stats.min; | ||||
|   for (int i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|   	const stl_facet &face = stl->facet_start[i]; | ||||
|     for (int j = 0; j < 3; ++ j) { | ||||
|       stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); | ||||
|       stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); | ||||
|     } | ||||
|   } | ||||
|   stl->stats.size = stl->stats.max - stl->stats.min; | ||||
|   stl->stats.bounding_diameter = stl->stats.size.norm(); | ||||
|   	if (stl->stats.number_of_facets == 0) | ||||
|   		return; | ||||
|   	stl->stats.min = stl->facet_start[0].vertex[0]; | ||||
|   	stl->stats.max = stl->stats.min; | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|   		const stl_facet &face = stl->facet_start[i]; | ||||
|     	for (int j = 0; j < 3; ++ j) { | ||||
|       		stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); | ||||
|       		stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); | ||||
|     	} | ||||
|   	} | ||||
|   	stl->stats.size = stl->stats.max - stl->stats.min; | ||||
|   	stl->stats.bounding_diameter = stl->stats.size.norm(); | ||||
| } | ||||
| 
 | ||||
| void stl_mirror_xy(stl_file *stl) | ||||
| { | ||||
|   if (stl->error)  | ||||
|   	return; | ||||
| 
 | ||||
|   for(int i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for(int j = 0; j < 3; j++) { | ||||
|       stl->facet_start[i].vertex[j](2) *= -1.0; | ||||
|     } | ||||
|   } | ||||
|   float temp_size = stl->stats.min(2); | ||||
|   stl->stats.min(2) = stl->stats.max(2); | ||||
|   stl->stats.max(2) = temp_size; | ||||
|   stl->stats.min(2) *= -1.0; | ||||
|   stl->stats.max(2) *= -1.0; | ||||
|   stl_reverse_all_facets(stl); | ||||
|   stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */ | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     	for (int j = 0; j < 3; ++ j) | ||||
|       		stl->facet_start[i].vertex[j](2) *= -1.0; | ||||
| 	float temp_size = stl->stats.min(2); | ||||
| 	stl->stats.min(2) = stl->stats.max(2); | ||||
| 	stl->stats.max(2) = temp_size; | ||||
| 	stl->stats.min(2) *= -1.0; | ||||
| 	stl->stats.max(2) *= -1.0; | ||||
| 	stl_reverse_all_facets(stl); | ||||
| 	stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */ | ||||
| } | ||||
| 
 | ||||
| void stl_mirror_yz(stl_file *stl) | ||||
| { | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   for (int i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for (int j = 0; j < 3; j++) { | ||||
|       stl->facet_start[i].vertex[j](0) *= -1.0; | ||||
|     } | ||||
|   } | ||||
|   float temp_size = stl->stats.min(0); | ||||
|   stl->stats.min(0) = stl->stats.max(0); | ||||
|   stl->stats.max(0) = temp_size; | ||||
|   stl->stats.min(0) *= -1.0; | ||||
|   stl->stats.max(0) *= -1.0; | ||||
|   stl_reverse_all_facets(stl); | ||||
|   stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */ | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
|     	for (int j = 0; j < 3; j++) | ||||
|       		stl->facet_start[i].vertex[j](0) *= -1.0; | ||||
| 	float temp_size = stl->stats.min(0); | ||||
| 	stl->stats.min(0) = stl->stats.max(0); | ||||
| 	stl->stats.max(0) = temp_size; | ||||
| 	stl->stats.min(0) *= -1.0; | ||||
| 	stl->stats.max(0) *= -1.0; | ||||
| 	stl_reverse_all_facets(stl); | ||||
| 	stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */ | ||||
| } | ||||
| 
 | ||||
| void stl_mirror_xz(stl_file *stl) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return; | ||||
| 
 | ||||
|   for (int i = 0; i < stl->stats.number_of_facets; i++) { | ||||
|     for (int j = 0; j < 3; j++) { | ||||
|       stl->facet_start[i].vertex[j](1) *= -1.0; | ||||
|     } | ||||
|   } | ||||
|   float temp_size = stl->stats.min(1); | ||||
|   stl->stats.min(1) = stl->stats.max(1); | ||||
|   stl->stats.max(1) = temp_size; | ||||
|   stl->stats.min(1) *= -1.0; | ||||
|   stl->stats.max(1) *= -1.0; | ||||
|   stl_reverse_all_facets(stl); | ||||
|   stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */ | ||||
| } | ||||
| 
 | ||||
| static float get_volume(stl_file *stl) | ||||
| { | ||||
|   if (stl->error) | ||||
|   	return 0; | ||||
| 
 | ||||
|   // Choose a point, any point as the reference.
 | ||||
|   stl_vertex p0 = stl->facet_start[0].vertex[0]; | ||||
|   float volume = 0.f; | ||||
|   for(uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     // Do dot product to get distance from point to plane.
 | ||||
|     float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); | ||||
|     float area   = get_area(&stl->facet_start[i]); | ||||
|     volume += (area * height) / 3.0f; | ||||
|   } | ||||
|   return volume; | ||||
| } | ||||
| 
 | ||||
| void stl_calculate_volume(stl_file *stl) | ||||
| { | ||||
|   if (stl->error) return; | ||||
|   stl->stats.volume = get_volume(stl); | ||||
|   if(stl->stats.volume < 0.0) { | ||||
|     stl_reverse_all_facets(stl); | ||||
|     stl->stats.volume = -stl->stats.volume; | ||||
|   } | ||||
| 	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) | ||||
| 		for (int j = 0; j < 3; ++ j) | ||||
| 			stl->facet_start[i].vertex[j](1) *= -1.0; | ||||
| 	float temp_size = stl->stats.min(1); | ||||
| 	stl->stats.min(1) = stl->stats.max(1); | ||||
| 	stl->stats.max(1) = temp_size; | ||||
| 	stl->stats.min(1) *= -1.0; | ||||
| 	stl->stats.max(1) *= -1.0; | ||||
| 	stl_reverse_all_facets(stl); | ||||
| 	stl->stats.facets_reversed -= stl->stats.number_of_facets;  // for not altering stats
 | ||||
| } | ||||
| 
 | ||||
| static float get_area(stl_facet *facet) | ||||
| { | ||||
|   /* cast to double before calculating cross product because large coordinates
 | ||||
|      can result in overflowing product | ||||
|     (bad area is responsible for bad volume and bad facets reversal) */ | ||||
|   double cross[3][3]; | ||||
|   for (int i = 0; i < 3; i++) { | ||||
|     cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - | ||||
|                  ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); | ||||
|     cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - | ||||
|                  ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); | ||||
|     cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - | ||||
|                  ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); | ||||
|   } | ||||
| 	/* cast to double before calculating cross product because large coordinates
 | ||||
| 	 can result in overflowing product | ||||
| 	(bad area is responsible for bad volume and bad facets reversal) */ | ||||
| 	double cross[3][3]; | ||||
| 	for (int i = 0; i < 3; i++) { | ||||
| 		cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - | ||||
| 	             	 ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); | ||||
| 		cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - | ||||
| 	             	 ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); | ||||
| 		cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - | ||||
| 	             	 ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); | ||||
| 	} | ||||
| 
 | ||||
|   stl_normal sum; | ||||
|   sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; | ||||
|   sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; | ||||
|   sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; | ||||
| 	stl_normal sum; | ||||
| 	sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; | ||||
| 	sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; | ||||
| 	sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; | ||||
| 
 | ||||
|   // This should already be done.  But just in case, let's do it again.
 | ||||
|   //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy.
 | ||||
|   stl_normal n; | ||||
|   stl_calculate_normal(n, facet); | ||||
|   stl_normalize_vector(n); | ||||
|   return 0.5f * n.dot(sum); | ||||
| 	// This should already be done.  But just in case, let's do it again.
 | ||||
| 	//FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy.
 | ||||
| 	stl_normal n; | ||||
| 	stl_calculate_normal(n, facet); | ||||
| 	stl_normalize_vector(n); | ||||
| 	return 0.5f * n.dot(sum); | ||||
| } | ||||
| 
 | ||||
| void stl_repair(stl_file *stl, | ||||
|                 int fixall_flag, | ||||
|                 int exact_flag, | ||||
|                 int tolerance_flag, | ||||
|                 float tolerance, | ||||
|                 int increment_flag, | ||||
|                 float increment, | ||||
|                 int nearby_flag, | ||||
|                 int iterations, | ||||
|                 int remove_unconnected_flag, | ||||
|                 int fill_holes_flag, | ||||
|                 int normal_directions_flag, | ||||
|                 int normal_values_flag, | ||||
|                 int reverse_all_flag, | ||||
|                 int verbose_flag) { | ||||
|    | ||||
|   int i; | ||||
|   int last_edges_fixed = 0; | ||||
| 
 | ||||
|   if (stl->error) return; | ||||
| 
 | ||||
|   if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag | ||||
|       || fill_holes_flag || normal_directions_flag) { | ||||
|     if (verbose_flag) | ||||
|       printf("Checking exact...\n"); | ||||
|     exact_flag = 1; | ||||
|     stl_check_facets_exact(stl); | ||||
|     stl->stats.facets_w_1_bad_edge = | ||||
|       (stl->stats.connected_facets_2_edge - | ||||
|        stl->stats.connected_facets_3_edge); | ||||
|     stl->stats.facets_w_2_bad_edge = | ||||
|       (stl->stats.connected_facets_1_edge - | ||||
|        stl->stats.connected_facets_2_edge); | ||||
|     stl->stats.facets_w_3_bad_edge = | ||||
|       (stl->stats.number_of_facets - | ||||
|        stl->stats.connected_facets_1_edge); | ||||
|   } | ||||
| 
 | ||||
|   if(nearby_flag || fixall_flag) { | ||||
|     if(!tolerance_flag) { | ||||
|       tolerance = stl->stats.shortest_edge; | ||||
|     } | ||||
|     if(!increment_flag) { | ||||
|       increment = stl->stats.bounding_diameter / 10000.0; | ||||
|     } | ||||
| 
 | ||||
|     if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { | ||||
|       for(i = 0; i < iterations; i++) { | ||||
|         if(stl->stats.connected_facets_3_edge < | ||||
|             stl->stats.number_of_facets) { | ||||
|           if (verbose_flag) | ||||
|             printf("\
 | ||||
| Checking nearby. Tolerance= %f Iteration=%d of %d...", | ||||
|                  tolerance, i + 1, iterations); | ||||
|           stl_check_facets_nearby(stl, tolerance); | ||||
|           if (verbose_flag) | ||||
|             printf("  Fixed %d edges.\n", | ||||
|                  stl->stats.edges_fixed - last_edges_fixed); | ||||
|           last_edges_fixed = stl->stats.edges_fixed; | ||||
|           tolerance += increment; | ||||
|         } else { | ||||
|           if (verbose_flag) | ||||
|             printf("\
 | ||||
| All facets connected.  No further nearby check necessary.\n"); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       if (verbose_flag) | ||||
|         printf("All facets connected.  No nearby check necessary.\n"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if(remove_unconnected_flag || fixall_flag || fill_holes_flag) { | ||||
|     if(stl->stats.connected_facets_3_edge <  stl->stats.number_of_facets) { | ||||
|       if (verbose_flag) | ||||
|         printf("Removing unconnected facets...\n"); | ||||
|       stl_remove_unconnected_facets(stl); | ||||
|     } else | ||||
|       if (verbose_flag) | ||||
|         printf("No unconnected need to be removed.\n"); | ||||
|   } | ||||
| 
 | ||||
|   if(fill_holes_flag || fixall_flag) { | ||||
|     if(stl->stats.connected_facets_3_edge <  stl->stats.number_of_facets) { | ||||
|       if (verbose_flag) | ||||
|         printf("Filling holes...\n"); | ||||
|       stl_fill_holes(stl); | ||||
|     } else | ||||
|       if (verbose_flag) | ||||
|         printf("No holes need to be filled.\n"); | ||||
|   } | ||||
| 
 | ||||
|   if(reverse_all_flag) { | ||||
|     if (verbose_flag) | ||||
|       printf("Reversing all facets...\n"); | ||||
|     stl_reverse_all_facets(stl); | ||||
|   } | ||||
| 
 | ||||
|   if(normal_directions_flag || fixall_flag) { | ||||
|     if (verbose_flag) | ||||
|       printf("Checking normal directions...\n"); | ||||
|     stl_fix_normal_directions(stl); | ||||
|   } | ||||
| 
 | ||||
|   if(normal_values_flag || fixall_flag) { | ||||
|     if (verbose_flag) | ||||
|       printf("Checking normal values...\n"); | ||||
|     stl_fix_normal_values(stl); | ||||
|   } | ||||
| 
 | ||||
|   /* Always calculate the volume.  It shouldn't take too long */ | ||||
|   if (verbose_flag) | ||||
|     printf("Calculating volume...\n"); | ||||
|   stl_calculate_volume(stl); | ||||
| 
 | ||||
|   if(exact_flag) { | ||||
|     if (verbose_flag) | ||||
|       printf("Verifying neighbors...\n"); | ||||
|     stl_verify_neighbors(stl); | ||||
|   } | ||||
| static float get_volume(stl_file *stl) | ||||
| { | ||||
|   	// Choose a point, any point as the reference.
 | ||||
|   	stl_vertex p0 = stl->facet_start[0].vertex[0]; | ||||
|   	float volume = 0.f; | ||||
|   	for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     	// Do dot product to get distance from point to plane.
 | ||||
|     	float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); | ||||
|     	float area   = get_area(&stl->facet_start[i]); | ||||
|     	volume += (area * height) / 3.0f; | ||||
|   	} | ||||
|   	return volume; | ||||
| } | ||||
| 
 | ||||
| void stl_calculate_volume(stl_file *stl) | ||||
| { | ||||
|   	stl->stats.volume = get_volume(stl); | ||||
|   	if (stl->stats.volume < 0.0) { | ||||
|     	stl_reverse_all_facets(stl); | ||||
|     	stl->stats.volume = -stl->stats.volume; | ||||
|   	} | ||||
| } | ||||
| 
 | ||||
| void stl_repair( | ||||
| 	stl_file *stl, | ||||
| 	bool fixall_flag, | ||||
| 	bool exact_flag, | ||||
| 	bool tolerance_flag, | ||||
| 	float tolerance, | ||||
| 	bool increment_flag, | ||||
| 	float increment, | ||||
| 	bool nearby_flag, | ||||
| 	int iterations, | ||||
| 	bool remove_unconnected_flag, | ||||
| 	bool fill_holes_flag, | ||||
| 	bool normal_directions_flag, | ||||
| 	bool normal_values_flag, | ||||
| 	bool reverse_all_flag, | ||||
| 	bool verbose_flag) | ||||
| { | ||||
| 	if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { | ||||
| 		if (verbose_flag) | ||||
| 		  	printf("Checking exact...\n"); | ||||
| 		exact_flag = true; | ||||
| 		stl_check_facets_exact(stl); | ||||
| 		stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); | ||||
| 		stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); | ||||
| 		stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); | ||||
| 	} | ||||
| 
 | ||||
|   	if (nearby_flag || fixall_flag) { | ||||
|     	if (! tolerance_flag) | ||||
|       		tolerance = stl->stats.shortest_edge; | ||||
|  	   	if (! increment_flag) | ||||
|       		increment = stl->stats.bounding_diameter / 10000.0; | ||||
|     } | ||||
| 
 | ||||
| 	if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { | ||||
| 	  	int last_edges_fixed = 0; | ||||
| 	  	for (int i = 0; i < iterations; ++ i) { | ||||
| 	    	if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { | ||||
| 	      		if (verbose_flag) | ||||
| 	        		printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); | ||||
| 	      		stl_check_facets_nearby(stl, tolerance); | ||||
| 	      		if (verbose_flag) | ||||
| 	        		printf("  Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed); | ||||
| 	      		last_edges_fixed = stl->stats.edges_fixed; | ||||
| 	      		tolerance += increment; | ||||
| 	    	} else { | ||||
| 	    		if (verbose_flag) | ||||
| 	        		printf("All facets connected.  No further nearby check necessary.\n"); | ||||
| 		      	break; | ||||
| 		    } | ||||
| 	  	} | ||||
| 	} else if (verbose_flag) | ||||
| 	    printf("All facets connected.  No nearby check necessary.\n"); | ||||
| 
 | ||||
| 	if (remove_unconnected_flag || fixall_flag || fill_holes_flag) { | ||||
| 		if (stl->stats.connected_facets_3_edge <  stl->stats.number_of_facets) { | ||||
| 	  		if (verbose_flag) | ||||
| 	    		printf("Removing unconnected facets...\n"); | ||||
| 	  		stl_remove_unconnected_facets(stl); | ||||
| 		} else if (verbose_flag) | ||||
| 	    	printf("No unconnected need to be removed.\n"); | ||||
| 	} | ||||
| 
 | ||||
| 	if (fill_holes_flag || fixall_flag) { | ||||
| 		if (stl->stats.connected_facets_3_edge <  stl->stats.number_of_facets) { | ||||
| 	  		if (verbose_flag) | ||||
| 	    		printf("Filling holes...\n"); | ||||
| 	  		stl_fill_holes(stl); | ||||
| 		} else if (verbose_flag) | ||||
| 	    	printf("No holes need to be filled.\n"); | ||||
| 	} | ||||
| 
 | ||||
| 	if (reverse_all_flag) { | ||||
| 		if (verbose_flag) | ||||
| 	  		printf("Reversing all facets...\n"); | ||||
| 		stl_reverse_all_facets(stl); | ||||
| 	} | ||||
| 
 | ||||
| 	if (normal_directions_flag || fixall_flag) { | ||||
| 		if (verbose_flag) | ||||
| 	  		printf("Checking normal directions...\n"); | ||||
| 		stl_fix_normal_directions(stl); | ||||
| 	} | ||||
| 
 | ||||
| 	if (normal_values_flag || fixall_flag) { | ||||
| 		if (verbose_flag) | ||||
| 	  		printf("Checking normal values...\n"); | ||||
| 		stl_fix_normal_values(stl); | ||||
| 	} | ||||
| 
 | ||||
|   	// Always calculate the volume.  It shouldn't take too long.
 | ||||
| 	if (verbose_flag) | ||||
| 		printf("Calculating volume...\n"); | ||||
| 	stl_calculate_volume(stl); | ||||
| 
 | ||||
| 	if (exact_flag) { | ||||
| 		if (verbose_flag) | ||||
| 	  		printf("Verifying neighbors...\n"); | ||||
| 		stl_verify_neighbors(stl); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -19,6 +19,6 @@ add_library(nowide STATIC | |||
|     nowide/windows.hpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(nowide SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) | ||||
| target_link_libraries(nowide PUBLIC boost_headeronly) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -55,12 +55,12 @@ endif() | |||
| 
 | ||||
| # Clipper backend is not enough on its own, it still needs some functions | ||||
| # from Boost geometry | ||||
| if(NOT Boost_INCLUDE_DIRS_FOUND) | ||||
| if(NOT Boost_FOUND) | ||||
|     find_package(Boost 1.58 REQUIRED) | ||||
|     # TODO automatic download of boost geometry headers | ||||
| endif() | ||||
| 
 | ||||
| target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS} ) | ||||
| target_link_libraries(clipperBackend INTERFACE Boost::boost ) | ||||
| #target_sources(ClipperBackend INTERFACE | ||||
| #    ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp | ||||
| #    ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp | ||||
|  |  | |||
|  | @ -130,13 +130,10 @@ add_library(libslic3r STATIC | |||
|     Print.hpp | ||||
|     PrintBase.cpp | ||||
|     PrintBase.hpp | ||||
|     PrintExport.hpp | ||||
|     PrintConfig.cpp | ||||
|     PrintConfig.hpp | ||||
|     PrintObject.cpp | ||||
|     PrintRegion.cpp | ||||
|     Rasterizer/Rasterizer.hpp | ||||
|     Rasterizer/Rasterizer.cpp | ||||
|     SLAPrint.cpp | ||||
|     SLAPrint.hpp | ||||
|     SLA/SLAAutoSupports.hpp | ||||
|  | @ -177,6 +174,10 @@ add_library(libslic3r STATIC | |||
|     SLA/SLARotfinder.cpp | ||||
|     SLA/SLABoostAdapter.hpp | ||||
|     SLA/SLASpatIndex.hpp | ||||
|     SLA/SLARaster.hpp | ||||
|     SLA/SLARaster.cpp | ||||
|     SLA/SLARasterWriter.hpp | ||||
|     SLA/SLARasterWriter.cpp | ||||
| ) | ||||
| 
 | ||||
| if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) | ||||
|  | @ -184,13 +185,12 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) | |||
| endif () | ||||
| 
 | ||||
| target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) | ||||
| target_include_directories(libslic3r SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) | ||||
| target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) | ||||
| target_link_libraries(libslic3r | ||||
|     libnest2d | ||||
|     admesh | ||||
|     miniz | ||||
|     ${Boost_LIBRARIES} | ||||
|     boost_libs | ||||
|     clipper | ||||
|     nowide | ||||
|     ${EXPAT_LIBRARIES} | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ | |||
| 
 | ||||
| #include "FillRectilinear3.hpp" | ||||
| 
 | ||||
|  #define SLIC3R_DEBUG | ||||
| // #define SLIC3R_DEBUG
 | ||||
| 
 | ||||
| // Make assert active if SLIC3R_DEBUG
 | ||||
| #ifdef SLIC3R_DEBUG | ||||
|  |  | |||
|  | @ -1489,10 +1489,10 @@ namespace Slic3r { | |||
|             } | ||||
| 
 | ||||
|             // splits volume out of imported geometry
 | ||||
|             unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; | ||||
|             ModelVolume* volume = object.add_volume(TriangleMesh()); | ||||
|             stl_file& stl = volume->mesh.stl; | ||||
|             stl.stats.type = inmemory; | ||||
| 			TriangleMesh triangle_mesh; | ||||
|             stl_file    &stl             = triangle_mesh.stl; | ||||
| 			unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; | ||||
| 			stl.stats.type = inmemory; | ||||
|             stl.stats.number_of_facets = (uint32_t)triangles_count; | ||||
|             stl.stats.original_num_facets = (int)stl.stats.number_of_facets; | ||||
|             stl_allocate(&stl); | ||||
|  | @ -1509,9 +1509,11 @@ namespace Slic3r { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             stl_get_size(&stl); | ||||
|             volume->mesh.repair(); | ||||
|             volume->center_geometry(); | ||||
| 			stl_get_size(&stl); | ||||
| 			triangle_mesh.repair(); | ||||
| 
 | ||||
| 			ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); | ||||
|             volume->center_geometry_after_creation(); | ||||
|             volume->calculate_convex_hull(); | ||||
| 
 | ||||
|             // apply volume's name and config data
 | ||||
|  | @ -1879,29 +1881,28 @@ namespace Slic3r { | |||
|             if (volume == nullptr) | ||||
|                 continue; | ||||
| 
 | ||||
| 			if (!volume->mesh().repaired) | ||||
| 				throw std::runtime_error("store_3mf() requires repair()"); | ||||
| 			if (!volume->mesh().has_shared_vertices()) | ||||
| 				throw std::runtime_error("store_3mf() requires shared vertices"); | ||||
| 
 | ||||
|             volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; | ||||
| 
 | ||||
|             if (!volume->mesh.repaired) | ||||
|                 volume->mesh.repair(); | ||||
| 
 | ||||
|             stl_file& stl = volume->mesh.stl; | ||||
|             if (stl.v_shared == nullptr) | ||||
|                 stl_generate_shared_vertices(&stl); | ||||
| 
 | ||||
|             if (stl.stats.shared_vertices == 0) | ||||
|             const indexed_triangle_set &its = volume->mesh().its; | ||||
|             if (its.vertices.empty()) | ||||
|             { | ||||
|                 add_error("Found invalid mesh"); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             vertices_count += stl.stats.shared_vertices; | ||||
|             vertices_count += its.vertices.size(); | ||||
| 
 | ||||
|             const Transform3d& matrix = volume->get_matrix(); | ||||
| 
 | ||||
|             for (int i = 0; i < stl.stats.shared_vertices; ++i) | ||||
|             for (size_t i = 0; i < its.vertices.size(); ++i) | ||||
|             { | ||||
|                 stream << "     <" << VERTEX_TAG << " "; | ||||
|                 Vec3f v = (matrix * stl.v_shared[i].cast<double>()).cast<float>(); | ||||
|                 Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>(); | ||||
|                 stream << "x=\"" << v(0) << "\" "; | ||||
|                 stream << "y=\"" << v(1) << "\" "; | ||||
|                 stream << "z=\"" << v(2) << "\" />\n"; | ||||
|  | @ -1920,19 +1921,19 @@ namespace Slic3r { | |||
|             VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); | ||||
|             assert(volume_it != volumes_offsets.end()); | ||||
| 
 | ||||
|             stl_file& stl = volume->mesh.stl; | ||||
|             const indexed_triangle_set &its = volume->mesh().its; | ||||
| 
 | ||||
|             // updates triangle offsets
 | ||||
|             volume_it->second.first_triangle_id = triangles_count; | ||||
|             triangles_count += stl.stats.number_of_facets; | ||||
|             triangles_count += its.indices.size(); | ||||
|             volume_it->second.last_triangle_id = triangles_count - 1; | ||||
| 
 | ||||
|             for (uint32_t i = 0; i < stl.stats.number_of_facets; ++i) | ||||
|             for (size_t i = 0; i < its.indices.size(); ++ i) | ||||
|             { | ||||
|                 stream << "     <" << TRIANGLE_TAG << " "; | ||||
|                 for (int j = 0; j < 3; ++j) | ||||
|                 { | ||||
|                     stream << "v" << j + 1 << "=\"" << stl.v_indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" "; | ||||
|                     stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; | ||||
|                 } | ||||
|                 stream << "/>\n"; | ||||
|             } | ||||
|  |  | |||
|  | @ -522,7 +522,8 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|     case NODE_TYPE_VOLUME: | ||||
|     { | ||||
| 		assert(m_object && m_volume); | ||||
|         stl_file &stl = m_volume->mesh.stl; | ||||
| 		TriangleMesh  mesh; | ||||
|         stl_file	 &stl = mesh.stl; | ||||
|         stl.stats.type = inmemory; | ||||
|         stl.stats.number_of_facets = int(m_volume_facets.size() / 3); | ||||
|         stl.stats.original_num_facets = stl.stats.number_of_facets; | ||||
|  | @ -533,8 +534,9 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|                 memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); | ||||
|         } | ||||
|         stl_get_size(&stl); | ||||
|         m_volume->mesh.repair(); | ||||
|         m_volume->center_geometry(); | ||||
|         mesh.repair(); | ||||
| 		m_volume->set_mesh(std::move(mesh)); | ||||
|         m_volume->center_geometry_after_creation(); | ||||
|         m_volume->calculate_convex_hull(); | ||||
|         m_volume_facets.clear(); | ||||
|         m_volume = nullptr; | ||||
|  | @ -923,23 +925,23 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|         int              num_vertices = 0; | ||||
|         for (ModelVolume *volume : object->volumes) { | ||||
|             vertices_offsets.push_back(num_vertices); | ||||
|             if (! volume->mesh.repaired)  | ||||
|             if (! volume->mesh().repaired) | ||||
|                 throw std::runtime_error("store_amf() requires repair()"); | ||||
|             auto &stl = volume->mesh.stl; | ||||
|             if (stl.v_shared == nullptr) | ||||
|                 stl_generate_shared_vertices(&stl); | ||||
| 			if (! volume->mesh().has_shared_vertices()) | ||||
| 				throw std::runtime_error("store_amf() requires shared vertices"); | ||||
|             const indexed_triangle_set &its = volume->mesh().its; | ||||
|             const Transform3d& matrix = volume->get_matrix(); | ||||
|             for (size_t i = 0; i < stl.stats.shared_vertices; ++i) { | ||||
|             for (size_t i = 0; i < its.vertices.size(); ++i) { | ||||
|                 stream << "         <vertex>\n"; | ||||
|                 stream << "           <coordinates>\n"; | ||||
|                 Vec3f v = (matrix * stl.v_shared[i].cast<double>()).cast<float>(); | ||||
|                 Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>(); | ||||
|                 stream << "             <x>" << v(0) << "</x>\n"; | ||||
|                 stream << "             <y>" << v(1) << "</y>\n"; | ||||
|                 stream << "             <z>" << v(2) << "</z>\n"; | ||||
|                 stream << "           </coordinates>\n"; | ||||
|                 stream << "         </vertex>\n"; | ||||
|             } | ||||
|             num_vertices += stl.stats.shared_vertices; | ||||
|             num_vertices += its.vertices.size(); | ||||
|         } | ||||
|         stream << "      </vertices>\n"; | ||||
|         for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { | ||||
|  | @ -956,10 +958,11 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|             if (volume->is_modifier()) | ||||
|                 stream << "        <metadata type=\"slic3r.modifier\">1</metadata>\n"; | ||||
|             stream << "        <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n"; | ||||
|             for (int i = 0; i < (int)volume->mesh.stl.stats.number_of_facets; ++i) { | ||||
| 			const indexed_triangle_set &its = volume->mesh().its; | ||||
|             for (size_t i = 0; i < (int)its.indices.size(); ++i) { | ||||
|                 stream << "        <triangle>\n"; | ||||
|                 for (int j = 0; j < 3; ++j) | ||||
|                 stream << "          <v" << j + 1 << ">" << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "</v" << j + 1 << ">\n"; | ||||
|                 stream << "          <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n"; | ||||
|                 stream << "        </triangle>\n"; | ||||
|             } | ||||
|             stream << "      </volume>\n"; | ||||
|  |  | |||
|  | @ -161,16 +161,15 @@ static void extract_model_from_archive( | |||
|         else { | ||||
|             // Header has been extracted. Now read the faces.
 | ||||
|             stl_file &stl = mesh.stl; | ||||
|             stl.error = 0; | ||||
|             stl.stats.type = inmemory; | ||||
|             stl.stats.number_of_facets = header.nTriangles; | ||||
|             stl.stats.original_num_facets = header.nTriangles; | ||||
|             stl_allocate(&stl); | ||||
|             if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) { | ||||
|                 memcpy((char*)stl.facet_start, data.data() + sizeof(StlHeader), 50 * header.nTriangles); | ||||
|                 memcpy((char*)stl.facet_start.data(), data.data() + sizeof(StlHeader), 50 * header.nTriangles); | ||||
|                 if (sizeof(stl_facet) > SIZEOF_STL_FACET) { | ||||
|                     // The stl.facet_start is not packed tightly. Unpack the array of stl_facets.
 | ||||
|                     unsigned char *data = (unsigned char*)stl.facet_start; | ||||
|                     unsigned char *data = (unsigned char*)stl.facet_start.data(); | ||||
|                     for (size_t i = header.nTriangles - 1; i > 0; -- i) | ||||
|                         memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); | ||||
|                 } | ||||
|  | @ -257,7 +256,7 @@ static void extract_model_from_archive( | |||
|             stl.stats.number_of_facets = (uint32_t)facets.size(); | ||||
|             stl.stats.original_num_facets = (int)facets.size(); | ||||
|             stl_allocate(&stl); | ||||
|             memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); | ||||
|             memcpy((void*)stl.facet_start.data(), facets.data(), facets.size() * 50); | ||||
|             stl_get_size(&stl); | ||||
|             mesh.repair(); | ||||
|             // Add a mesh to a model.
 | ||||
|  |  | |||
|  | @ -17,8 +17,7 @@ namespace Slic3r { | |||
| bool load_stl(const char *path, Model *model, const char *object_name_in) | ||||
| { | ||||
|     TriangleMesh mesh; | ||||
|     mesh.ReadSTLFile(path); | ||||
|     if (mesh.stl.error) { | ||||
|     if (! mesh.ReadSTLFile(path)) { | ||||
| //    die "Failed to open $file\n" if !-e $path;
 | ||||
|         return false; | ||||
|     } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include <mutex>        // for std::lock_guard
 | ||||
| #include <functional>   // for std::function
 | ||||
| #include <utility>      // for std::forward
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -182,6 +183,14 @@ public: | |||
|     inline bool empty() const { return size() == 0; } | ||||
| }; | ||||
| 
 | ||||
| template<class C> bool all_of(const C &container) { | ||||
|     return std::all_of(container.begin(), | ||||
|                        container.end(), | ||||
|                        [](const typename C::value_type &v) { | ||||
|                            return static_cast<bool>(v); | ||||
|                        }); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // MTUTILS_HPP
 | ||||
|  |  | |||
|  | @ -160,12 +160,6 @@ Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig | |||
|     return model; | ||||
| } | ||||
| 
 | ||||
| void Model::repair() | ||||
| { | ||||
|     for (ModelObject *o : this->objects) | ||||
|         o->repair(); | ||||
| } | ||||
| 
 | ||||
| ModelObject* Model::add_object() | ||||
| { | ||||
|     this->objects.emplace_back(new ModelObject(this)); | ||||
|  | @ -472,7 +466,7 @@ bool Model::looks_like_multipart_object() const | |||
|         if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) | ||||
|             return false; | ||||
|         for (const ModelVolume *vol : obj->volumes) { | ||||
|             double zmin_this = vol->mesh.bounding_box().min(2); | ||||
|             double zmin_this = vol->mesh().bounding_box().min(2); | ||||
|             if (zmin == std::numeric_limits<double>::max()) | ||||
|                 zmin = zmin_this; | ||||
|             else if (std::abs(zmin - zmin_this) > EPSILON) | ||||
|  | @ -679,7 +673,7 @@ ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) | |||
| { | ||||
|     ModelVolume* v = new ModelVolume(this, mesh); | ||||
|     this->volumes.push_back(v); | ||||
|     v->center_geometry(); | ||||
|     v->center_geometry_after_creation(); | ||||
|     this->invalidate_bounding_box(); | ||||
|     return v; | ||||
| } | ||||
|  | @ -688,7 +682,7 @@ ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh) | |||
| { | ||||
|     ModelVolume* v = new ModelVolume(this, std::move(mesh)); | ||||
|     this->volumes.push_back(v); | ||||
|     v->center_geometry(); | ||||
|     v->center_geometry_after_creation(); | ||||
|     this->invalidate_bounding_box(); | ||||
|     return v; | ||||
| } | ||||
|  | @ -697,8 +691,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other) | |||
| { | ||||
|     ModelVolume* v = new ModelVolume(this, other); | ||||
|     this->volumes.push_back(v); | ||||
|     v->center_geometry(); | ||||
|     this->invalidate_bounding_box(); | ||||
| 	// The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull.
 | ||||
| //	v->center_geometry_after_creation();
 | ||||
| //    this->invalidate_bounding_box();
 | ||||
|     return v; | ||||
| } | ||||
| 
 | ||||
|  | @ -706,7 +701,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&me | |||
| { | ||||
|     ModelVolume* v = new ModelVolume(this, other, std::move(mesh)); | ||||
|     this->volumes.push_back(v); | ||||
|     v->center_geometry(); | ||||
|     v->center_geometry_after_creation(); | ||||
|     this->invalidate_bounding_box(); | ||||
|     return v; | ||||
| } | ||||
|  | @ -827,7 +822,7 @@ TriangleMesh ModelObject::raw_mesh() const | |||
|     for (const ModelVolume *v : this->volumes) | ||||
|         if (v->is_model_part()) | ||||
|         { | ||||
|             TriangleMesh vol_mesh(v->mesh); | ||||
|             TriangleMesh vol_mesh(v->mesh()); | ||||
|             vol_mesh.transform(v->get_matrix()); | ||||
|             mesh.merge(vol_mesh); | ||||
|         } | ||||
|  | @ -840,7 +835,7 @@ TriangleMesh ModelObject::full_raw_mesh() const | |||
|     TriangleMesh mesh; | ||||
|     for (const ModelVolume *v : this->volumes) | ||||
|     { | ||||
|         TriangleMesh vol_mesh(v->mesh); | ||||
|         TriangleMesh vol_mesh(v->mesh()); | ||||
|         vol_mesh.transform(v->get_matrix()); | ||||
|         mesh.merge(vol_mesh); | ||||
|     } | ||||
|  | @ -854,7 +849,7 @@ const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const | |||
|         m_raw_mesh_bounding_box.reset(); | ||||
|         for (const ModelVolume *v : this->volumes) | ||||
|             if (v->is_model_part()) | ||||
|                 m_raw_mesh_bounding_box.merge(v->mesh.transformed_bounding_box(v->get_matrix())); | ||||
|                 m_raw_mesh_bounding_box.merge(v->mesh().transformed_bounding_box(v->get_matrix())); | ||||
|     } | ||||
|     return m_raw_mesh_bounding_box; | ||||
| } | ||||
|  | @ -863,7 +858,7 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const | |||
| { | ||||
| 	BoundingBoxf3 bb; | ||||
| 	for (const ModelVolume *v : this->volumes) | ||||
| 		bb.merge(v->mesh.transformed_bounding_box(v->get_matrix())); | ||||
| 		bb.merge(v->mesh().transformed_bounding_box(v->get_matrix())); | ||||
| 	return bb; | ||||
| } | ||||
| 
 | ||||
|  | @ -881,7 +876,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const | |||
|         for (const ModelVolume *v : this->volumes) | ||||
|         { | ||||
|             if (v->is_model_part()) | ||||
|                 m_raw_bounding_box.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); | ||||
|                 m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); | ||||
|         } | ||||
|     } | ||||
| 	return m_raw_bounding_box; | ||||
|  | @ -895,7 +890,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ | |||
|     for (ModelVolume *v : this->volumes) | ||||
|     { | ||||
|         if (v->is_model_part()) | ||||
|             bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); | ||||
|             bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); | ||||
|     } | ||||
|     return bb; | ||||
| } | ||||
|  | @ -908,21 +903,20 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const | |||
|     Points pts; | ||||
|     for (const ModelVolume *v : this->volumes) | ||||
|         if (v->is_model_part()) { | ||||
|             const stl_file &stl = v->mesh.stl; | ||||
|             Transform3d trafo = trafo_instance * v->get_matrix(); | ||||
|             if (stl.v_shared == nullptr) { | ||||
| 			const indexed_triangle_set &its = v->mesh().its; | ||||
| 			if (its.vertices.empty()) { | ||||
|                 // Using the STL faces.
 | ||||
|                 for (unsigned int i = 0; i < stl.stats.number_of_facets; ++ i) { | ||||
|                     const stl_facet &facet = stl.facet_start[i]; | ||||
| 				const stl_file& stl = v->mesh().stl; | ||||
| 				for (const stl_facet &facet : stl.facet_start) | ||||
|                     for (size_t j = 0; j < 3; ++ j) { | ||||
|                         Vec3d p = trafo * facet.vertex[j].cast<double>(); | ||||
|                         pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 // Using the shared vertices should be a bit quicker than using the STL faces.
 | ||||
|                 for (int i = 0; i < stl.stats.shared_vertices; ++ i) {            | ||||
|                     Vec3d p = trafo * stl.v_shared[i].cast<double>(); | ||||
|                 for (size_t i = 0; i < its.vertices.size(); ++ i) { | ||||
|                     Vec3d p = trafo * its.vertices[i].cast<double>(); | ||||
|                     pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); | ||||
|                 } | ||||
|             } | ||||
|  | @ -1039,6 +1033,7 @@ void ModelObject::mirror(Axis axis) | |||
|     this->invalidate_bounding_box(); | ||||
| } | ||||
| 
 | ||||
| // This method could only be called before the meshes of this ModelVolumes are not shared!
 | ||||
| void ModelObject::scale_mesh(const Vec3d &versor) | ||||
| { | ||||
|     for (ModelVolume *v : this->volumes) | ||||
|  | @ -1062,14 +1057,14 @@ size_t ModelObject::facets_count() const | |||
|     size_t num = 0; | ||||
|     for (const ModelVolume *v : this->volumes) | ||||
|         if (v->is_model_part()) | ||||
|             num += v->mesh.stl.stats.number_of_facets; | ||||
|             num += v->mesh().stl.stats.number_of_facets; | ||||
|     return num; | ||||
| } | ||||
| 
 | ||||
| bool ModelObject::needed_repair() const | ||||
| { | ||||
|     for (const ModelVolume *v : this->volumes) | ||||
|         if (v->is_model_part() && v->mesh.needed_repair()) | ||||
|         if (v->is_model_part() && v->mesh().needed_repair()) | ||||
|             return true; | ||||
|     return false; | ||||
| } | ||||
|  | @ -1135,11 +1130,12 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b | |||
| 
 | ||||
|             // Transform the mesh by the combined transformation matrix.
 | ||||
|             // Flip the triangles in case the composite transformation is left handed.
 | ||||
|             volume->mesh.transform(instance_matrix * volume_matrix, true); | ||||
| 			TriangleMesh mesh(volume->mesh()); | ||||
| 			mesh.transform(instance_matrix * volume_matrix, true); | ||||
| 			volume->reset_mesh(); | ||||
| 
 | ||||
|             // Perform cut
 | ||||
|             volume->mesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|             TriangleMeshSlicer tms(&volume->mesh); | ||||
|             TriangleMeshSlicer tms(&mesh); | ||||
|             tms.cut(float(z), &upper_mesh, &lower_mesh); | ||||
| 
 | ||||
|             // Reset volume transformation except for offset
 | ||||
|  | @ -1158,14 +1154,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b | |||
| 
 | ||||
|             if (keep_upper && upper_mesh.facets_count() > 0) { | ||||
|                 ModelVolume* vol = upper->add_volume(upper_mesh); | ||||
|                 vol->name = volume->name; | ||||
|                 vol->config         = volume->config; | ||||
|                 vol->name	= volume->name; | ||||
|                 vol->config = volume->config; | ||||
|                 vol->set_material(volume->material_id(), *volume->material()); | ||||
|             } | ||||
|             if (keep_lower && lower_mesh.facets_count() > 0) { | ||||
|                 ModelVolume* vol = lower->add_volume(lower_mesh); | ||||
|                 vol->name = volume->name; | ||||
|                 vol->config         = volume->config; | ||||
|                 vol->name	= volume->name; | ||||
|                 vol->config = volume->config; | ||||
|                 vol->set_material(volume->material_id(), *volume->material()); | ||||
| 
 | ||||
|                 // Compute the lower part instances' bounding boxes to figure out where to place
 | ||||
|  | @ -1233,7 +1229,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) | |||
|     } | ||||
|      | ||||
|     ModelVolume* volume = this->volumes.front(); | ||||
|     TriangleMeshPtrs meshptrs = volume->mesh.split(); | ||||
|     TriangleMeshPtrs meshptrs = volume->mesh().split(); | ||||
|     for (TriangleMesh *mesh : meshptrs) { | ||||
|         mesh->repair(); | ||||
|          | ||||
|  | @ -1260,12 +1256,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects) | |||
|     return; | ||||
| } | ||||
| 
 | ||||
| void ModelObject::repair() | ||||
| { | ||||
|     for (ModelVolume *v : this->volumes) | ||||
|         v->mesh.repair(); | ||||
| } | ||||
| 
 | ||||
| // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
 | ||||
| // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
 | ||||
| // This situation is solved by baking in the instance transformation into the mesh vertices.
 | ||||
|  | @ -1295,8 +1285,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) | |||
| 
 | ||||
|     // Adjust the meshes.
 | ||||
|     // Transformation to be applied to the meshes.
 | ||||
|     Eigen::Matrix3d    mesh_trafo_3x3           = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); | ||||
| 	Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); | ||||
|     Eigen::Matrix3d mesh_trafo_3x3           = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); | ||||
| 	Transform3d     volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); | ||||
|     for (ModelVolume *model_volume : this->volumes) { | ||||
|         const Geometry::Transformation volume_trafo = model_volume->get_transformation(); | ||||
|         bool   volume_left_handed        = volume_trafo.is_left_handed(); | ||||
|  | @ -1306,7 +1296,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) | |||
|         double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; | ||||
|         // Transform the mesh.
 | ||||
| 		Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); | ||||
| 		model_volume->transform_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); | ||||
|         // Following method creates a new shared_ptr<TriangleMesh>
 | ||||
| 		model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); | ||||
|         // Reset the rotation, scaling and mirroring.
 | ||||
|         model_volume->set_rotation(Vec3d(0., 0., 0.)); | ||||
|         model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor)); | ||||
|  | @ -1347,13 +1338,9 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const | |||
| 
 | ||||
|         Transform3d mv = mi * v->get_matrix(); | ||||
|         const TriangleMesh& hull = v->get_convex_hull(); | ||||
|         for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f) | ||||
|         { | ||||
|             const stl_facet* facet = hull.stl.facet_start + f; | ||||
|             min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[0].cast<double>())); | ||||
|             min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[1].cast<double>())); | ||||
|             min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[2].cast<double>())); | ||||
|         } | ||||
| 		for (const stl_facet &facet : hull.stl.facet_start) | ||||
| 			for (int i = 0; i < 3; ++ i) | ||||
| 				min_z = std::min(min_z, (mv * facet.vertex[i].cast<double>()).z()); | ||||
|     } | ||||
| 
 | ||||
|     return min_z + inst->get_offset(Z); | ||||
|  | @ -1452,7 +1439,7 @@ std::string ModelObject::get_export_filename() const | |||
| stl_stats ModelObject::get_object_stl_stats() const | ||||
| { | ||||
|     if (this->volumes.size() == 1) | ||||
|         return this->volumes[0]->mesh.stl.stats; | ||||
|         return this->volumes[0]->mesh().stl.stats; | ||||
| 
 | ||||
|     stl_stats full_stats; | ||||
|     memset(&full_stats, 0, sizeof(stl_stats)); | ||||
|  | @ -1463,7 +1450,7 @@ stl_stats ModelObject::get_object_stl_stats() const | |||
|         if (volume->id() == this->volumes[0]->id()) | ||||
|             continue; | ||||
| 
 | ||||
|         const stl_stats& stats = volume->mesh.stl.stats; | ||||
|         const stl_stats& stats = volume->mesh().stl.stats; | ||||
| 
 | ||||
|         // initialize full_stats (for repaired errors)
 | ||||
|         full_stats.degenerate_facets    += stats.degenerate_facets; | ||||
|  | @ -1531,30 +1518,30 @@ bool ModelVolume::is_splittable() const | |||
| { | ||||
|     // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
 | ||||
|     if (m_is_splittable == -1) | ||||
|         m_is_splittable = (int)mesh.is_splittable(); | ||||
|         m_is_splittable = (int)this->mesh().is_splittable(); | ||||
| 
 | ||||
|     return m_is_splittable == 1; | ||||
| } | ||||
| 
 | ||||
| void ModelVolume::center_geometry() | ||||
| void ModelVolume::center_geometry_after_creation() | ||||
| { | ||||
|     Vec3d shift = mesh.bounding_box().center(); | ||||
|     Vec3d shift = this->mesh().bounding_box().center(); | ||||
|     if (!shift.isApprox(Vec3d::Zero())) | ||||
|     { | ||||
|         mesh.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); | ||||
|         m_convex_hull.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); | ||||
|         m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); | ||||
|         m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); | ||||
|         translate(shift); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ModelVolume::calculate_convex_hull() | ||||
| { | ||||
|     m_convex_hull = mesh.convex_hull_3d(); | ||||
|     m_convex_hull = std::make_shared<TriangleMesh>(this->mesh().convex_hull_3d()); | ||||
| } | ||||
| 
 | ||||
| int ModelVolume::get_mesh_errors_count() const | ||||
| { | ||||
|     const stl_stats& stats = this->mesh.stl.stats; | ||||
|     const stl_stats& stats = this->mesh().stl.stats; | ||||
| 
 | ||||
|     return  stats.degenerate_facets + stats.edges_fixed     + stats.facets_removed + | ||||
|             stats.facets_added      + stats.facets_reversed + stats.backwards_edges; | ||||
|  | @ -1562,7 +1549,7 @@ int ModelVolume::get_mesh_errors_count() const | |||
| 
 | ||||
| const TriangleMesh& ModelVolume::get_convex_hull() const | ||||
| { | ||||
|     return m_convex_hull; | ||||
|     return *m_convex_hull.get(); | ||||
| } | ||||
| 
 | ||||
| ModelVolumeType ModelVolume::type_from_string(const std::string &s) | ||||
|  | @ -1602,7 +1589,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) | |||
| // This is useful to assign different materials to different volumes of an object.
 | ||||
| size_t ModelVolume::split(unsigned int max_extruders) | ||||
| { | ||||
|     TriangleMeshPtrs meshptrs = this->mesh.split(); | ||||
|     TriangleMeshPtrs meshptrs = this->mesh().split(); | ||||
|     if (meshptrs.size() <= 1) { | ||||
|         delete meshptrs.front(); | ||||
|         return 1; | ||||
|  | @ -1619,7 +1606,7 @@ size_t ModelVolume::split(unsigned int max_extruders) | |||
|         mesh->repair(); | ||||
|         if (idx == 0) | ||||
|         { | ||||
|             this->mesh = std::move(*mesh); | ||||
|             this->set_mesh(std::move(*mesh)); | ||||
|             this->calculate_convex_hull(); | ||||
|             // Assign a new unique ID, so that a new GLVolume will be generated.
 | ||||
|             this->set_new_unique_id(); | ||||
|  | @ -1628,7 +1615,7 @@ size_t ModelVolume::split(unsigned int max_extruders) | |||
|             this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh))); | ||||
| 
 | ||||
|         this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); | ||||
|         this->object->volumes[ivolume]->center_geometry(); | ||||
|         this->object->volumes[ivolume]->center_geometry_after_creation(); | ||||
|         this->object->volumes[ivolume]->translate(offset); | ||||
|         this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); | ||||
|         this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders)); | ||||
|  | @ -1694,24 +1681,33 @@ void ModelVolume::mirror(Axis axis) | |||
|     set_mirror(mirror); | ||||
| } | ||||
| 
 | ||||
| // This method could only be called before the meshes of this ModelVolumes are not shared!
 | ||||
| void ModelVolume::scale_geometry(const Vec3d& versor) | ||||
| { | ||||
|     mesh.scale(versor); | ||||
|     m_convex_hull.scale(versor); | ||||
|     m_mesh->scale(versor); | ||||
|     m_convex_hull->scale(versor); | ||||
| } | ||||
| 
 | ||||
| void ModelVolume::transform_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) | ||||
| void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) | ||||
| { | ||||
|     this->mesh.transform(mesh_trafo, fix_left_handed); | ||||
|     this->m_convex_hull.transform(mesh_trafo, fix_left_handed); | ||||
| 	TriangleMesh mesh = this->mesh(); | ||||
| 	mesh.transform(mesh_trafo, fix_left_handed); | ||||
| 	this->set_mesh(std::move(mesh)); | ||||
|     TriangleMesh convex_hull = this->get_convex_hull(); | ||||
|     convex_hull.transform(mesh_trafo, fix_left_handed); | ||||
|     this->m_convex_hull = std::make_shared<TriangleMesh>(std::move(convex_hull)); | ||||
|     // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
 | ||||
|     this->set_new_unique_id(); | ||||
| } | ||||
| 
 | ||||
| void ModelVolume::transform_mesh(const Matrix3d &matrix, bool fix_left_handed) | ||||
| void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_handed) | ||||
| { | ||||
| 	this->mesh.transform(matrix, fix_left_handed); | ||||
| 	this->m_convex_hull.transform(matrix, fix_left_handed); | ||||
| 	TriangleMesh mesh = this->mesh(); | ||||
| 	mesh.transform(matrix, fix_left_handed); | ||||
| 	this->set_mesh(std::move(mesh)); | ||||
|     TriangleMesh convex_hull = this->get_convex_hull(); | ||||
|     convex_hull.transform(matrix, fix_left_handed); | ||||
|     this->m_convex_hull = std::make_shared<TriangleMesh>(std::move(convex_hull)); | ||||
|     // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
 | ||||
|     this->set_new_unique_id(); | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,9 @@ | |||
| #include "Point.hpp" | ||||
| #include "TriangleMesh.hpp" | ||||
| #include "Slicing.hpp" | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
|  | @ -261,6 +263,7 @@ public: | |||
|     void rotate(double angle, const Vec3d& axis); | ||||
|     void mirror(Axis axis); | ||||
| 
 | ||||
|     // This method could only be called before the meshes of this ModelVolumes are not shared!
 | ||||
|     void scale_mesh(const Vec3d& versor); | ||||
| 
 | ||||
|     size_t materials_count() const; | ||||
|  | @ -268,7 +271,6 @@ public: | |||
|     bool needed_repair() const; | ||||
|     ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false);    // Note: z is in world coordinates
 | ||||
|     void split(ModelObjectPtrs* new_objects); | ||||
|     void repair(); | ||||
|     // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
 | ||||
|     // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
 | ||||
|     // This situation is solved by baking in the instance transformation into the mesh vertices.
 | ||||
|  | @ -340,7 +342,12 @@ class ModelVolume : public ModelBase | |||
| public: | ||||
|     std::string         name; | ||||
|     // The triangular model.
 | ||||
|     TriangleMesh        mesh; | ||||
|     const TriangleMesh& mesh() const { return *m_mesh.get(); } | ||||
|     void                set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<TriangleMesh>(mesh); } | ||||
|     void                set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<TriangleMesh>(std::move(mesh)); } | ||||
|     void                set_mesh(std::shared_ptr<TriangleMesh> &mesh) { m_mesh = mesh; } | ||||
|     void                set_mesh(std::unique_ptr<TriangleMesh> &&mesh) { m_mesh = std::move(mesh); } | ||||
| 	void				reset_mesh() { m_mesh = std::make_shared<TriangleMesh>(); } | ||||
|     // Configuration parameters specific to an object model geometry or a modifier volume, 
 | ||||
|     // overriding the global Slic3r settings and the ModelObject settings.
 | ||||
|     DynamicPrintConfig  config; | ||||
|  | @ -377,13 +384,16 @@ public: | |||
|     void                rotate(double angle, const Vec3d& axis); | ||||
|     void                mirror(Axis axis); | ||||
| 
 | ||||
|     // This method could only be called before the meshes of this ModelVolumes are not shared!
 | ||||
|     void                scale_geometry(const Vec3d& versor); | ||||
| 
 | ||||
|     // translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box
 | ||||
|     void                center_geometry(); | ||||
|     // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
 | ||||
|     // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
 | ||||
|     void                center_geometry_after_creation(); | ||||
| 
 | ||||
|     void                calculate_convex_hull(); | ||||
|     const TriangleMesh& get_convex_hull() const; | ||||
|     std::shared_ptr<const TriangleMesh> get_convex_hull_shared_ptr() const { return m_convex_hull; } | ||||
|     // Get count of errors in the mesh
 | ||||
|     int                 get_mesh_errors_count() const; | ||||
| 
 | ||||
|  | @ -430,18 +440,20 @@ protected: | |||
| 
 | ||||
| 	explicit ModelVolume(const ModelVolume &rhs) = default; | ||||
|     void     set_model_object(ModelObject *model_object) { object = model_object; } | ||||
|     void     transform_mesh(const Transform3d& t, bool fix_left_handed); | ||||
|     void     transform_mesh(const Matrix3d& m, bool fix_left_handed); | ||||
|     void     transform_this_mesh(const Transform3d& t, bool fix_left_handed); | ||||
|     void     transform_this_mesh(const Matrix3d& m, bool fix_left_handed); | ||||
| 
 | ||||
| private: | ||||
|     // Parent object owning this ModelVolume.
 | ||||
|     ModelObject*            object; | ||||
|     ModelObject*                    object; | ||||
|     // The triangular model.
 | ||||
|     std::shared_ptr<TriangleMesh>   m_mesh; | ||||
|     // Is it an object to be printed, or a modifier volume?
 | ||||
|     ModelVolumeType         m_type; | ||||
|     t_model_material_id     m_material_id; | ||||
|     ModelVolumeType                 m_type; | ||||
|     t_model_material_id             m_material_id; | ||||
|     // The convex hull of this model's mesh.
 | ||||
|     TriangleMesh             m_convex_hull; | ||||
|     Geometry::Transformation m_transformation; | ||||
|     std::shared_ptr<TriangleMesh>   m_convex_hull; | ||||
|     Geometry::Transformation        m_transformation; | ||||
| 
 | ||||
|     // flag to optimize the checking if the volume is splittable
 | ||||
|     //     -1   ->   is unknown value (before first cheking)
 | ||||
|  | @ -449,24 +461,24 @@ private: | |||
|     //      1   ->   is splittable
 | ||||
|     mutable int               m_is_splittable{ -1 }; | ||||
| 
 | ||||
| 	ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) | ||||
| 	ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object) | ||||
|     { | ||||
|         if (mesh.stl.stats.number_of_facets > 1) | ||||
|             calculate_convex_hull(); | ||||
|     } | ||||
|     ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : | ||||
| 		mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(ModelVolumeType::MODEL_PART), object(object) {} | ||||
| 		m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {} | ||||
| 
 | ||||
|     // Copying an existing volume, therefore this volume will get a copy of the ID assigned.
 | ||||
|     ModelVolume(ModelObject *object, const ModelVolume &other) : | ||||
|         ModelBase(other), // copy the ID
 | ||||
|         name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) | ||||
|         name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) | ||||
|     { | ||||
|         this->set_material_id(other.material_id()); | ||||
|     } | ||||
|     // Providing a new mesh, therefore this volume will get a new unique ID assigned.
 | ||||
|     ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : | ||||
|         name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) | ||||
|         name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) | ||||
|     { | ||||
|         this->set_material_id(other.material_id()); | ||||
|         if (mesh.stl.stats.number_of_facets > 1) | ||||
|  | @ -597,10 +609,6 @@ public: | |||
|     static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); | ||||
|     static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); | ||||
| 
 | ||||
|     /// Repair the ModelObjects of the current Model.
 | ||||
|     /// This function calls repair function on each TriangleMesh of each model object volume
 | ||||
|     void         repair(); | ||||
| 
 | ||||
|     // Add a new ModelObject to this Model, generate a new ID for this ModelObject.
 | ||||
|     ModelObject* add_object(); | ||||
|     ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); | ||||
|  |  | |||
|  | @ -2258,6 +2258,20 @@ void PrintConfigDef::init_sla_params() | |||
|     def->min = 100; | ||||
|     def->set_default_value(new ConfigOptionInt(1440)); | ||||
| 
 | ||||
|     def = this->add("display_mirror_x", coBool); | ||||
|     def->full_label = L("Display horizontal mirroring"); | ||||
|     def->label = L("Mirror horizontally"); | ||||
|     def->tooltip = L("Enable horizontal mirroring of output images"); | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionBool(true)); | ||||
| 
 | ||||
|     def = this->add("display_mirror_y", coBool); | ||||
|     def->full_label = L("Display vertical mirroring"); | ||||
|     def->label = L("Mirror vertically"); | ||||
|     def->tooltip = L("Enable vertical mirroring of output images"); | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionBool(false)); | ||||
| 
 | ||||
|     def = this->add("display_orientation", coEnum); | ||||
|     def->label = L("Display orientation"); | ||||
|     def->tooltip = L("Set the actual LCD display orientation inside the SLA printer." | ||||
|  |  | |||
|  | @ -1083,6 +1083,8 @@ public: | |||
|     ConfigOptionInt                         display_pixels_x; | ||||
|     ConfigOptionInt                         display_pixels_y; | ||||
|     ConfigOptionEnum<SLADisplayOrientation> display_orientation; | ||||
|     ConfigOptionBool                        display_mirror_x; | ||||
|     ConfigOptionBool                        display_mirror_y; | ||||
|     ConfigOptionFloats                      relative_correction; | ||||
|     ConfigOptionFloat                       absolute_correction; | ||||
|     ConfigOptionFloat                       gamma_correction; | ||||
|  | @ -1099,6 +1101,8 @@ protected: | |||
|         OPT_PTR(display_height); | ||||
|         OPT_PTR(display_pixels_x); | ||||
|         OPT_PTR(display_pixels_y); | ||||
|         OPT_PTR(display_mirror_x); | ||||
|         OPT_PTR(display_mirror_y); | ||||
|         OPT_PTR(display_orientation); | ||||
|         OPT_PTR(relative_correction); | ||||
|         OPT_PTR(absolute_correction); | ||||
|  |  | |||
|  | @ -1,327 +0,0 @@ | |||
| #ifndef PRINTEXPORT_HPP | ||||
| #define PRINTEXPORT_HPP | ||||
| 
 | ||||
| // For png export of the sliced model
 | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| 
 | ||||
| #include "Rasterizer/Rasterizer.hpp" | ||||
| //#include <tbb/parallel_for.h>
 | ||||
| //#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| // Used for addressing parameters of FilePrinter::set_statistics()
 | ||||
| enum ePrintStatistics | ||||
| { | ||||
|     psUsedMaterial = 0, | ||||
|     psNumFade, | ||||
|     psNumSlow, | ||||
|     psNumFast, | ||||
| 
 | ||||
|     psCnt | ||||
| }; | ||||
| 
 | ||||
| enum class FilePrinterFormat { | ||||
|     SLA_PNGZIP, | ||||
|     SVG | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * Interface for a file printer of the slices. Implementation can be an SVG | ||||
|  * or PNG printer or any other format. | ||||
|  * | ||||
|  * The format argument specifies the output format of the printer and it enables | ||||
|  * different implementations of this class template for each supported format. | ||||
|  * | ||||
|  */ | ||||
| template<FilePrinterFormat format> | ||||
| class FilePrinter { | ||||
| public: | ||||
| 
 | ||||
|     // Draw a polygon which is a polygon inside a slice on the specified layer.
 | ||||
|     void draw_polygon(const ExPolygon& p, unsigned lyr); | ||||
|     void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr); | ||||
| 
 | ||||
|     // Tell the printer how many layers should it consider.
 | ||||
|     void layers(unsigned layernum); | ||||
| 
 | ||||
|     // Get the number of layers in the print.
 | ||||
|     unsigned layers() const; | ||||
| 
 | ||||
|     /* Switch to a particular layer. If there where less layers then the
 | ||||
|      * specified layer number than an appropriate number of layers will be | ||||
|      * allocated in the printer. | ||||
|      */ | ||||
|     void begin_layer(unsigned layer); | ||||
| 
 | ||||
|     // Allocate a new layer on top of the last and switch to it.
 | ||||
|     void begin_layer(); | ||||
| 
 | ||||
|     /*
 | ||||
|      * Finish the selected layer. It means that no drawing is allowed on that | ||||
|      * layer anymore. This fact can be used to prepare the file system output | ||||
|      * data like png comprimation and so on. | ||||
|      */ | ||||
|     void finish_layer(unsigned layer); | ||||
| 
 | ||||
|     // Finish the top layer.
 | ||||
|     void finish_layer(); | ||||
| 
 | ||||
|     // Save all the layers into the file (or dir) specified in the path argument
 | ||||
|     // An optional project name can be added to be used for the layer file names
 | ||||
|     void save(const std::string& path, const std::string& projectname = ""); | ||||
| 
 | ||||
|     // Save only the selected layer to the file specified in path argument.
 | ||||
|     void save_layer(unsigned lyr, const std::string& path); | ||||
| }; | ||||
| 
 | ||||
| // Provokes static_assert in the right way.
 | ||||
| template<class T = void> struct VeryFalse { static const bool value = false; }; | ||||
| 
 | ||||
| // This can be explicitly implemented in the gui layer or the default Zipper
 | ||||
| // API in libslic3r with minz.
 | ||||
| template<class Fmt> class LayerWriter { | ||||
| public: | ||||
| 
 | ||||
|     LayerWriter(const std::string& /*zipfile_path*/) | ||||
|     { | ||||
|         static_assert(VeryFalse<Fmt>::value, | ||||
|                       "No layer writer implementation provided!"); | ||||
|     } | ||||
| 
 | ||||
|     // Should create a new file within the zip with the given filename. It
 | ||||
|     // should also finish any previous entry.
 | ||||
|     void next_entry(const std::string& /*fname*/) {} | ||||
| 
 | ||||
|     // Should create a new file within the archive and write the provided data.
 | ||||
|     void binary_entry(const std::string& /*fname*/, | ||||
|                       const std::uint8_t* buf, size_t len); | ||||
| 
 | ||||
|     // Test whether the object can still be used for writing.
 | ||||
|     bool is_ok() { return false; } | ||||
| 
 | ||||
|     // Write some data (text) into the current file (entry) within the archive.
 | ||||
|     template<class T> LayerWriter& operator<<(T&& /*arg*/) { | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     // Flush the current entry into the archive.
 | ||||
|     void finalize() {} | ||||
| }; | ||||
| 
 | ||||
| // Implementation for PNG raster output
 | ||||
| // Be aware that if a large number of layers are allocated, it can very well
 | ||||
| // exhaust the available memory especially on 32 bit platform.
 | ||||
| template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP> | ||||
| { | ||||
|     struct Layer { | ||||
|         Raster raster; | ||||
|         RawBytes rawbytes; | ||||
| 
 | ||||
|         Layer() {} | ||||
| 
 | ||||
|         Layer(const Layer&) = delete; | ||||
|         Layer(Layer&& m): | ||||
|             raster(std::move(m.raster)) {} | ||||
|     }; | ||||
| 
 | ||||
|     // We will save the compressed PNG data into stringstreams which can be done
 | ||||
|     // in parallel. Later we can write every layer to the disk sequentially.
 | ||||
|     std::vector<Layer> m_layers_rst; | ||||
|     Raster::Resolution m_res; | ||||
|     Raster::PixelDim m_pxdim; | ||||
|     double m_exp_time_s = .0, m_exp_time_first_s = .0; | ||||
|     double m_layer_height = .0; | ||||
|     Raster::Origin m_o = Raster::Origin::TOP_LEFT; | ||||
|     double m_gamma; | ||||
| 
 | ||||
|     double m_used_material = 0.0; | ||||
|     int    m_cnt_fade_layers = 0; | ||||
|     int    m_cnt_slow_layers = 0; | ||||
|     int    m_cnt_fast_layers = 0; | ||||
| 
 | ||||
|     std::string createIniContent(const std::string& projectname) { | ||||
|         using std::string; | ||||
|         using std::to_string; | ||||
| 
 | ||||
|         auto expt_str = to_string(m_exp_time_s); | ||||
|         auto expt_first_str = to_string(m_exp_time_first_s); | ||||
|         auto layerh_str = to_string(m_layer_height); | ||||
| 
 | ||||
|         const std::string cnt_fade_layers = to_string(m_cnt_fade_layers); | ||||
|         const std::string cnt_slow_layers = to_string(m_cnt_slow_layers); | ||||
|         const std::string cnt_fast_layers = to_string(m_cnt_fast_layers); | ||||
|         const std::string used_material   = to_string(m_used_material); | ||||
| 
 | ||||
|         return string( | ||||
|         "action = print\n" | ||||
|         "jobDir = ") + projectname + "\n" + | ||||
|         "expTime = " + expt_str + "\n" | ||||
|         "expTimeFirst = " + expt_first_str + "\n" | ||||
|         "numFade = " + cnt_fade_layers + "\n" | ||||
|         "layerHeight = " + layerh_str + "\n" | ||||
|         "usedMaterial = " + used_material + "\n" | ||||
|         "numSlow = " + cnt_slow_layers + "\n" | ||||
|         "numFast = " + cnt_fast_layers + "\n"; | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     enum RasterOrientation { | ||||
|         RO_LANDSCAPE, | ||||
|         RO_PORTRAIT | ||||
|     }; | ||||
| 
 | ||||
|     // We will play with the raster's coordinate origin parameter. When the
 | ||||
|     // printer should print in landscape mode it should have the Y axis flipped
 | ||||
|     // because the layers should be displayed upside down. PNG has its
 | ||||
|     // coordinate origin in the top-left corner so normally the Raster objects
 | ||||
|     // should be instantiated with the TOP_LEFT flag. However, in landscape mode
 | ||||
|     // we do want the pictures to be upside down so we will make BOTTOM_LEFT
 | ||||
|     // type rasters and the PNG format will do the flipping automatically.
 | ||||
| 
 | ||||
|     // In case of portrait images, we have to rotate the image by a 90 degrees
 | ||||
|     // and flip the y axis. To get the correct upside-down orientation of the
 | ||||
|     // slice images, we can flip the x and y coordinates of the input polygons
 | ||||
|     // and do the Y flipping of the image. This will generate the correct
 | ||||
|     // orientation in portrait mode.
 | ||||
| 
 | ||||
|     inline FilePrinter(double width_mm, double height_mm, | ||||
|                        unsigned width_px, unsigned height_px, | ||||
|                        double layer_height, | ||||
|                        double exp_time, double exp_time_first, | ||||
|                        RasterOrientation ro = RO_PORTRAIT, | ||||
|                        double gamma = 1.0): | ||||
|         m_res(width_px, height_px), | ||||
|         m_pxdim(width_mm/width_px, height_mm/height_px), | ||||
|         m_exp_time_s(exp_time), | ||||
|         m_exp_time_first_s(exp_time_first), | ||||
|         m_layer_height(layer_height), | ||||
| 
 | ||||
|         // Here is the trick with the orientation.
 | ||||
|         m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : | ||||
|                                 Raster::Origin::TOP_LEFT ), | ||||
|         m_gamma(gamma) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     FilePrinter(const FilePrinter& ) = delete; | ||||
|     FilePrinter(FilePrinter&& m): | ||||
|         m_layers_rst(std::move(m.m_layers_rst)), | ||||
|         m_res(m.m_res), | ||||
|         m_pxdim(m.m_pxdim) {} | ||||
| 
 | ||||
|     inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } | ||||
|     inline unsigned layers() const { return unsigned(m_layers_rst.size()); } | ||||
| 
 | ||||
|     inline void draw_polygon(const ExPolygon& p, unsigned lyr) { | ||||
|         assert(lyr < m_layers_rst.size()); | ||||
|         m_layers_rst[lyr].raster.draw(p); | ||||
|     } | ||||
| 
 | ||||
|     inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) { | ||||
|         assert(lyr < m_layers_rst.size()); | ||||
|         m_layers_rst[lyr].raster.draw(p); | ||||
|     } | ||||
| 
 | ||||
|     inline void begin_layer(unsigned lyr) { | ||||
|         if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); | ||||
|         m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o, m_gamma); | ||||
|     } | ||||
| 
 | ||||
|     inline void begin_layer() { | ||||
|         m_layers_rst.emplace_back(); | ||||
|         m_layers_rst.front().raster.reset(m_res, m_pxdim, m_o, m_gamma); | ||||
|     } | ||||
| 
 | ||||
|     inline void finish_layer(unsigned lyr_id) { | ||||
|         assert(lyr_id < m_layers_rst.size()); | ||||
|         m_layers_rst[lyr_id].rawbytes = | ||||
|                 m_layers_rst[lyr_id].raster.save(Raster::Compression::PNG); | ||||
|         m_layers_rst[lyr_id].raster.reset(); | ||||
|     } | ||||
| 
 | ||||
|     inline void finish_layer() { | ||||
|         if(!m_layers_rst.empty()) { | ||||
|             m_layers_rst.back().rawbytes = | ||||
|                     m_layers_rst.back().raster.save(Raster::Compression::PNG); | ||||
|             m_layers_rst.back().raster.reset(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     template<class LyrFmt> | ||||
|     inline void save(const std::string& fpath, const std::string& prjname = "") | ||||
|     { | ||||
|         try { | ||||
|             LayerWriter<LyrFmt> writer(fpath); | ||||
|             if(!writer.is_ok()) return; | ||||
| 
 | ||||
|             std::string project = prjname.empty()? | ||||
|                        boost::filesystem::path(fpath).stem().string() : prjname; | ||||
| 
 | ||||
|             writer.next_entry("config.ini"); | ||||
|             if(!writer.is_ok()) return; | ||||
| 
 | ||||
|             writer << createIniContent(project); | ||||
| 
 | ||||
|             for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++) | ||||
|             { | ||||
|                 if(m_layers_rst[i].rawbytes.size() > 0) { | ||||
|                     char lyrnum[6]; | ||||
|                     std::sprintf(lyrnum, "%.5d", i); | ||||
|                     auto zfilename = project + lyrnum + ".png"; | ||||
|                     if(!writer.is_ok()) break; | ||||
| 
 | ||||
|                     writer.binary_entry(zfilename, | ||||
|                                         m_layers_rst[i].rawbytes.data(), | ||||
|                                         m_layers_rst[i].rawbytes.size()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             writer.finalize(); | ||||
|         } catch(std::exception& e) { | ||||
|             BOOST_LOG_TRIVIAL(error) << e.what(); | ||||
|             // Rethrow the exception
 | ||||
|             throw; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void save_layer(unsigned lyr, const std::string& path) { | ||||
|         unsigned i = lyr; | ||||
|         assert(i < m_layers_rst.size()); | ||||
| 
 | ||||
|         char lyrnum[6]; | ||||
|         std::sprintf(lyrnum, "%.5d", lyr); | ||||
|         std::string loc = path + "layer" + lyrnum + ".png"; | ||||
| 
 | ||||
|         std::fstream out(loc, std::fstream::out | std::fstream::binary); | ||||
|         if(out.good()) { | ||||
|             m_layers_rst[i].raster.save(out, Raster::Compression::PNG); | ||||
|         } else { | ||||
|             BOOST_LOG_TRIVIAL(error) << "Can't create file for layer"; | ||||
|         } | ||||
| 
 | ||||
|         out.close(); | ||||
|         m_layers_rst[i].raster.reset(); | ||||
|     } | ||||
| 
 | ||||
|     void set_statistics(const std::vector<double> statistics) | ||||
|     { | ||||
|         if (statistics.size() != psCnt) | ||||
|             return; | ||||
| 
 | ||||
|         m_used_material   = statistics[psUsedMaterial]; | ||||
|         m_cnt_fade_layers = int(statistics[psNumFade]); | ||||
|         m_cnt_slow_layers = int(statistics[psNumSlow]); | ||||
|         m_cnt_fast_layers = int(statistics[psNumFast]); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // PRINTEXPORT_HPP
 | ||||
|  | @ -1797,7 +1797,7 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, | |||
|     if (! volumes.empty()) { | ||||
|         // Compose mesh.
 | ||||
|         //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
 | ||||
| 		TriangleMesh mesh(volumes.front()->mesh); | ||||
| 		TriangleMesh mesh(volumes.front()->mesh()); | ||||
|         mesh.transform(volumes.front()->get_matrix(), true); | ||||
| 		assert(mesh.repaired); | ||||
| 		if (volumes.size() == 1 && mesh.repaired) { | ||||
|  | @ -1806,7 +1806,7 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, | |||
| 		} | ||||
|         for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) { | ||||
|             const ModelVolume &model_volume = *volumes[idx_volume]; | ||||
|             TriangleMesh vol_mesh(model_volume.mesh); | ||||
|             TriangleMesh vol_mesh(model_volume.mesh()); | ||||
|             vol_mesh.transform(model_volume.get_matrix(), true); | ||||
|             mesh.merge(vol_mesh); | ||||
|         } | ||||
|  | @ -1815,10 +1815,11 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, | |||
|             // apply XY shift
 | ||||
|             mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0); | ||||
|             // perform actual slicing
 | ||||
|             TriangleMeshSlicer mslicer; | ||||
|             const Print *print = this->print(); | ||||
|             auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); | ||||
|             mesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|             // TriangleMeshSlicer needs shared vertices, also this calls the repair() function.
 | ||||
|             mesh.require_shared_vertices(); | ||||
|             TriangleMeshSlicer mslicer; | ||||
|             mslicer.init(&mesh, callback); | ||||
| 			mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); | ||||
|             m_print->throw_if_canceled(); | ||||
|  | @ -1832,7 +1833,7 @@ std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z, | |||
|     std::vector<ExPolygons> layers; | ||||
|     // Compose mesh.
 | ||||
|     //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
 | ||||
|     TriangleMesh mesh(volume.mesh); | ||||
|     TriangleMesh mesh(volume.mesh()); | ||||
|     mesh.transform(volume.get_matrix(), true); | ||||
| 	if (mesh.repaired) { | ||||
| 		//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
 | ||||
|  | @ -1846,7 +1847,8 @@ std::vector<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &z, | |||
|         TriangleMeshSlicer mslicer; | ||||
|         const Print *print = this->print(); | ||||
|         auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); | ||||
|         mesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|         // TriangleMeshSlicer needs the shared vertices.
 | ||||
|         mesh.require_shared_vertices(); | ||||
|         mslicer.init(&mesh, callback); | ||||
|         mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); | ||||
|         m_print->throw_if_canceled(); | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
| 
 | ||||
|     // Shorthand for the vertex arrays
 | ||||
|     auto& upoints = upper.points, &lpoints = lower.points; | ||||
|     auto& rpts = ret.points; auto& rfaces = ret.indices; | ||||
|     auto& rpts = ret.points; auto& ind = ret.indices; | ||||
| 
 | ||||
|     // If the Z levels are flipped, or the offset difference is negative, we
 | ||||
|     // will interpret that as the triangles normals should be inverted.
 | ||||
|  | @ -61,10 +61,11 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
| 
 | ||||
|     // Copy the points into the mesh, convert them from 2D to 3D
 | ||||
|     rpts.reserve(upoints.size() + lpoints.size()); | ||||
|     rfaces.reserve(2*upoints.size() + 2*lpoints.size()); | ||||
|     const double sf = SCALING_FACTOR; | ||||
|     for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm); | ||||
|     for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm); | ||||
|     ind.reserve(2 * upoints.size() + 2 * lpoints.size()); | ||||
|     for (auto &p : upoints) | ||||
|         rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); | ||||
|     for (auto &p : lpoints) | ||||
|         rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); | ||||
| 
 | ||||
|     // Create pointing indices into vertex arrays. u-upper, l-lower
 | ||||
|     size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; | ||||
|  | @ -121,9 +122,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
|         case Proceed::UPPER: | ||||
|             if(!ustarted || uidx != uendidx) { // there are vertices remaining
 | ||||
|                 // Get the 3D vertices in order
 | ||||
|                 const Vec3d& p_up1 = rpts[size_t(uidx)]; | ||||
|                 const Vec3d& p_low = rpts[size_t(lidx)]; | ||||
|                 const Vec3d& p_up2 = rpts[size_t(unextidx)]; | ||||
|                 const Vec3d& p_up1 = rpts[uidx]; | ||||
|                 const Vec3d& p_low = rpts[lidx]; | ||||
|                 const Vec3d& p_up2 = rpts[unextidx]; | ||||
| 
 | ||||
|                 // Calculate fitness: the average of the two connecting edges
 | ||||
|                 double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); | ||||
|  | @ -133,8 +134,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
|                 if(current_fit > prev_fit) { // fit is worse than previously
 | ||||
|                     proceed = Proceed::LOWER; | ||||
|                 } else {    // good to go, create the triangle
 | ||||
|                     inverted? rfaces.emplace_back(unextidx, lidx, uidx) : | ||||
|                               rfaces.emplace_back(uidx, lidx, unextidx) ; | ||||
|                     inverted | ||||
|                         ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) | ||||
|                         : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); | ||||
| 
 | ||||
|                     // Increment the iterators, rotate if necessary
 | ||||
|                     ++uidx; ++unextidx; | ||||
|  | @ -150,9 +152,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
|         case Proceed::LOWER: | ||||
|             // Mode with lower segment, upper vertex. Same structure:
 | ||||
|             if(!lstarted || lidx != lendidx) { | ||||
|                 const Vec3d& p_low1 = rpts[size_t(lidx)]; | ||||
|                 const Vec3d& p_low2 = rpts[size_t(lnextidx)]; | ||||
|                 const Vec3d& p_up   = rpts[size_t(uidx)]; | ||||
|                 const Vec3d& p_low1 = rpts[lidx]; | ||||
|                 const Vec3d& p_low2 = rpts[lnextidx]; | ||||
|                 const Vec3d& p_up   = rpts[uidx]; | ||||
| 
 | ||||
|                 double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); | ||||
|                 double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); | ||||
|  | @ -161,8 +163,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
|                 if(current_fit > prev_fit) { | ||||
|                     proceed = Proceed::UPPER; | ||||
|                 } else { | ||||
|                     inverted? rfaces.emplace_back(uidx, lnextidx, lidx) : | ||||
|                               rfaces.emplace_back(lidx, lnextidx, uidx); | ||||
|                     inverted | ||||
|                         ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) | ||||
|                         : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); | ||||
| 
 | ||||
|                     ++lidx; ++lnextidx; | ||||
|                     if(lnextidx == rpts.size()) lnextidx = offs; | ||||
|  | @ -200,7 +203,7 @@ void offset(ExPolygon& sh, coord_t distance) { | |||
|     } | ||||
| 
 | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = 0.01*mm(1); | ||||
|     offs.ArcTolerance = 0.01*scaled(1.0); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, jtRound, etClosedPolygon); | ||||
|     offs.AddPaths(holes, jtRound, etClosedPolygon); | ||||
|  | @ -303,16 +306,6 @@ ExPolygons unify(const ExPolygons& shapes) { | |||
|     return retv; | ||||
| } | ||||
| 
 | ||||
| /// Only a debug function to generate top and bottom plates from a 2D shape.
 | ||||
| /// It is not used in the algorithm directly.
 | ||||
| inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { | ||||
|     auto lower = triangulate_expolygon_3d(poly); | ||||
|     auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true); | ||||
|     Contour3D ret; | ||||
|     ret.merge(lower); ret.merge(upper); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /// This method will create a rounded edge around a flat polygon in 3d space.
 | ||||
| /// 'base_plate' parameter is the target plate.
 | ||||
| /// 'radius' is the radius of the edges.
 | ||||
|  | @ -358,7 +351,7 @@ Contour3D round_edges(const ExPolygon& base_plate, | |||
|         double x2 = xx*xx; | ||||
|         double stepy = std::sqrt(r2 - x2); | ||||
| 
 | ||||
|         offset(ob, s*mm(xx)); | ||||
|         offset(ob, s*scaled(xx)); | ||||
|         wh = ceilheight_mm - radius_mm + stepy; | ||||
| 
 | ||||
|         Contour3D pwalls; | ||||
|  | @ -382,7 +375,7 @@ Contour3D round_edges(const ExPolygon& base_plate, | |||
|             double xx = radius_mm - i*stepx; | ||||
|             double x2 = xx*xx; | ||||
|             double stepy = std::sqrt(r2 - x2); | ||||
|             offset(ob, s*mm(xx)); | ||||
|             offset(ob, s*scaled(xx)); | ||||
|             wh = ceilheight_mm - radius_mm - stepy; | ||||
| 
 | ||||
|             Contour3D pwalls; | ||||
|  | @ -402,41 +395,6 @@ Contour3D round_edges(const ExPolygon& base_plate, | |||
|     return curvedwalls; | ||||
| } | ||||
| 
 | ||||
| /// Generating the concave part of the 3D pool with the bottom plate and the
 | ||||
| /// side walls.
 | ||||
| Contour3D inner_bed(const ExPolygon& poly, | ||||
|                     double depth_mm, | ||||
|                     double begin_h_mm = 0) | ||||
| { | ||||
|     Contour3D bottom; | ||||
|     Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm); | ||||
|     bottom.merge(triangles); | ||||
| 
 | ||||
|     coord_t depth = mm(depth_mm); | ||||
|     coord_t begin_h = mm(begin_h_mm); | ||||
| 
 | ||||
|     auto lines = poly.lines(); | ||||
| 
 | ||||
|     // Generate outer walls
 | ||||
|     auto fp = [](const Point& p, Point::coord_type z) { | ||||
|         return unscale(x(p), y(p), z); | ||||
|     }; | ||||
| 
 | ||||
|     for(auto& l : lines) { | ||||
|         auto s = coord_t(bottom.points.size()); | ||||
| 
 | ||||
|         bottom.points.emplace_back(fp(l.a, -depth + begin_h)); | ||||
|         bottom.points.emplace_back(fp(l.b, -depth + begin_h)); | ||||
|         bottom.points.emplace_back(fp(l.a, begin_h)); | ||||
|         bottom.points.emplace_back(fp(l.b, begin_h)); | ||||
| 
 | ||||
|         bottom.indices.emplace_back(s + 3, s + 1, s); | ||||
|         bottom.indices.emplace_back(s + 2, s + 3, s); | ||||
|     } | ||||
| 
 | ||||
|     return bottom; | ||||
| } | ||||
| 
 | ||||
| inline Point centroid(Points& pp) { | ||||
|     Point c; | ||||
|     switch(pp.size()) { | ||||
|  | @ -518,7 +476,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | |||
|         double dx = x(c) - x(cc), dy = y(c) - y(cc); | ||||
|         double l = std::sqrt(dx * dx + dy * dy); | ||||
|         double nx = dx / l, ny = dy / l; | ||||
|         double max_dist = mm(max_dist_mm); | ||||
|         double max_dist = scaled(max_dist_mm); | ||||
| 
 | ||||
|         ExPolygon& expo = punion[idx++]; | ||||
|         BoundingBox querybb(expo); | ||||
|  | @ -534,10 +492,10 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | |||
|         ctour.reserve(3); | ||||
|         ctour.emplace_back(cc); | ||||
| 
 | ||||
|         Point d(coord_t(mm(1)*nx), coord_t(mm(1)*ny)); | ||||
|         Point d(coord_t(scaled(1.)*nx), coord_t(scaled(1.)*ny)); | ||||
|         ctour.emplace_back(c + Point( -y(d),  x(d) )); | ||||
|         ctour.emplace_back(c + Point(  y(d), -x(d) )); | ||||
|         offset(r, mm(1)); | ||||
|         offset(r, scaled(1.)); | ||||
| 
 | ||||
|         return r; | ||||
|     }); | ||||
|  | @ -569,15 +527,16 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, | |||
|     // Now we have to unify all slice layers which can be an expensive operation
 | ||||
|     // so we will try to simplify the polygons
 | ||||
|     ExPolygons tmp; tmp.reserve(count); | ||||
|     for(ExPolygons& o : out) for(ExPolygon& e : o) { | ||||
|         auto&& exss = e.simplify(0.1/SCALING_FACTOR); | ||||
|         for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); | ||||
|     } | ||||
|     for(ExPolygons& o : out) | ||||
|         for(ExPolygon& e : o) { | ||||
|             auto&& exss = e.simplify(scaled(0.1)); | ||||
|             for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); | ||||
|         } | ||||
| 
 | ||||
|     ExPolygons utmp = unify(tmp); | ||||
| 
 | ||||
|     for(auto& o : utmp) { | ||||
|         auto&& smp = o.simplify(0.1/SCALING_FACTOR); | ||||
|         auto&& smp = o.simplify(scaled(0.1)); | ||||
|         output.insert(output.end(), smp.begin(), smp.end()); | ||||
|     } | ||||
| } | ||||
|  | @ -607,11 +566,11 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|     const double bottom_offs    = (thickness + wingheight) / std::tan(slope); | ||||
| 
 | ||||
|     // scaled values
 | ||||
|     const coord_t s_thickness   = mm(thickness); | ||||
|     const coord_t s_eradius     = mm(cfg.edge_radius_mm); | ||||
|     const coord_t s_thickness   = scaled(thickness); | ||||
|     const coord_t s_eradius     = scaled(cfg.edge_radius_mm); | ||||
|     const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); | ||||
|     const coord_t s_wingdist    = mm(wingdist); | ||||
|     const coord_t s_bottom_offs = mm(bottom_offs); | ||||
|     const coord_t s_wingdist    = scaled(wingdist); | ||||
|     const coord_t s_bottom_offs = scaled(bottom_offs); | ||||
| 
 | ||||
|     auto& thrcl = cfg.throw_on_cancel; | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,11 +11,6 @@ | |||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| using coord_t = Point::coord_type; | ||||
| 
 | ||||
| /// get the scaled clipper units for a millimeter value
 | ||||
| inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); } | ||||
| 
 | ||||
| /// Get x and y coordinates (because we are eigenizing...)
 | ||||
| inline coord_t x(const Point& p) { return p(0); } | ||||
| inline coord_t y(const Point& p) { return p(1); } | ||||
|  | @ -36,12 +31,10 @@ inline coord_t x(const Vec3crd& p) { return p(0); } | |||
| inline coord_t y(const Vec3crd& p) { return p(1); } | ||||
| inline coord_t z(const Vec3crd& p) { return p(2); } | ||||
| 
 | ||||
| using Indices = std::vector<Vec3crd>; | ||||
| 
 | ||||
| /// Intermediate struct for a 3D mesh
 | ||||
| struct Contour3D { | ||||
|     Pointf3s points; | ||||
|     Indices indices; | ||||
|     std::vector<Vec3i> indices; | ||||
| 
 | ||||
|     void merge(const Contour3D& ctr) { | ||||
|         auto s3 = coord_t(points.size()); | ||||
|  |  | |||
|  | @ -1,5 +1,10 @@ | |||
| #include "Rasterizer.hpp" | ||||
| #include <ExPolygon.hpp> | ||||
| #ifndef SLARASTER_CPP | ||||
| #define SLARASTER_CPP | ||||
| 
 | ||||
| #include <functional> | ||||
| 
 | ||||
| #include "SLARaster.hpp" | ||||
| #include "libslic3r/ExPolygon.hpp" | ||||
| #include <libnest2d/backends/clipper/clipper_polygon.hpp> | ||||
| 
 | ||||
| // For rasterizing
 | ||||
|  | @ -19,11 +24,13 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| const Polygon& contour(const ExPolygon& p) { return p.contour; } | ||||
| const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } | ||||
| inline const Polygon& contour(const ExPolygon& p) { return p.contour; } | ||||
| inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } | ||||
| 
 | ||||
| const Polygons& holes(const ExPolygon& p) { return p.holes; } | ||||
| const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } | ||||
| inline const Polygons& holes(const ExPolygon& p) { return p.holes; } | ||||
| inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| class Raster::Impl { | ||||
| public: | ||||
|  | @ -39,7 +46,7 @@ public: | |||
|     static const TPixel ColorWhite; | ||||
|     static const TPixel ColorBlack; | ||||
| 
 | ||||
|     using Origin = Raster::Origin; | ||||
|     using Format = Raster::Format; | ||||
| 
 | ||||
| private: | ||||
|     Raster::Resolution m_resolution; | ||||
|  | @ -52,16 +59,21 @@ private: | |||
|     TRendererAA m_renderer; | ||||
|      | ||||
|     std::function<double(double)> m_gammafn; | ||||
|     Origin m_o; | ||||
|     std::array<bool, 2> m_mirror; | ||||
|     Format m_fmt = Format::PNG; | ||||
|      | ||||
|     inline void flipy(agg::path_storage& path) const { | ||||
|         path.flip_y(0, m_resolution.height_px); | ||||
|     } | ||||
|      | ||||
|     inline void flipx(agg::path_storage& path) const { | ||||
|         path.flip_x(0, m_resolution.width_px); | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, | ||||
|                 Origin o, double gamma = 1.0): | ||||
|                 const std::array<bool, 2>& mirror, double gamma = 1.0): | ||||
|         m_resolution(res),  | ||||
| //        m_pxdim(pd), 
 | ||||
|         m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm), | ||||
|  | @ -72,7 +84,7 @@ public: | |||
|         m_pixfmt(m_rbuf), | ||||
|         m_raw_renderer(m_pixfmt), | ||||
|         m_renderer(m_raw_renderer), | ||||
|         m_o(o) | ||||
|         m_mirror(mirror) | ||||
|     { | ||||
|         m_renderer.color(ColorWhite); | ||||
|          | ||||
|  | @ -81,6 +93,19 @@ public: | |||
|          | ||||
|         clear(); | ||||
|     } | ||||
|      | ||||
|     inline Impl(const Raster::Resolution& res,  | ||||
|                 const Raster::PixelDim &pd, | ||||
|                 Format fmt,  | ||||
|                 double gamma = 1.0):  | ||||
|         Impl(res, pd, {false, false}, gamma)  | ||||
|     { | ||||
|         switch (fmt) { | ||||
|         case Format::PNG: m_mirror = {false, true}; break; | ||||
|         case Format::RAW: m_mirror = {false, false}; break; | ||||
|         } | ||||
|         m_fmt = fmt; | ||||
|     } | ||||
| 
 | ||||
|     template<class P> void draw(const P &poly) { | ||||
|         agg::rasterizer_scanline_aa<> ras; | ||||
|  | @ -89,14 +114,16 @@ public: | |||
|         ras.gamma(m_gammafn); | ||||
| 
 | ||||
|         auto&& path = to_path(contour(poly)); | ||||
| 
 | ||||
|         if(m_o == Origin::TOP_LEFT) flipy(path); | ||||
|          | ||||
|         if(m_mirror[X]) flipx(path); | ||||
|         if(m_mirror[Y]) flipy(path); | ||||
| 
 | ||||
|         ras.add_path(path); | ||||
| 
 | ||||
|         for(auto& h : holes(poly)) { | ||||
|             auto&& holepath = to_path(h); | ||||
|             if(m_o == Origin::TOP_LEFT) flipy(holepath); | ||||
|             if(m_mirror[X]) flipx(holepath); | ||||
|             if(m_mirror[Y]) flipy(holepath); | ||||
|             ras.add_path(holepath); | ||||
|         } | ||||
| 
 | ||||
|  | @ -108,11 +135,11 @@ public: | |||
|     } | ||||
| 
 | ||||
|     inline TBuffer& buffer()  { return m_buf; } | ||||
|      | ||||
|     inline Format format() const { return m_fmt; } | ||||
| 
 | ||||
|     inline const Raster::Resolution resolution() { return m_resolution; } | ||||
| 
 | ||||
|     inline Origin origin() const /*noexcept*/ { return m_o; } | ||||
| 
 | ||||
|     | ||||
| private: | ||||
|     inline double getPx(const Point& p) { | ||||
|         return p(0) * m_pxdim_scaled.w_mm; | ||||
|  | @ -154,30 +181,30 @@ private: | |||
| const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); | ||||
| const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); | ||||
| 
 | ||||
| Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o, double g): | ||||
|     m_impl(new Impl(r, pd, o, g)) {} | ||||
| template<> Raster::Raster() { reset(); }; | ||||
| Raster::~Raster() = default; | ||||
| 
 | ||||
| Raster::Raster() {} | ||||
| // Raster::Raster(Raster &&m) = default;
 | ||||
| // Raster& Raster::operator=(Raster&&) = default;
 | ||||
| 
 | ||||
| Raster::~Raster() {} | ||||
| 
 | ||||
| Raster::Raster(Raster &&m): | ||||
|     m_impl(std::move(m.m_impl)) {} | ||||
| 
 | ||||
| void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,  | ||||
|                    double g) | ||||
| { | ||||
|     // Free up the unnecessary memory and make sure it stays clear after
 | ||||
|     // an exception
 | ||||
|     auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT; | ||||
|     reset(r, pd, o, g); | ||||
| // FIXME: remove after migrating to higher version of windows compiler
 | ||||
| Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {} | ||||
| Raster& Raster::operator=(Raster &&m) { | ||||
|     m_impl = std::move(m.m_impl); return *this; | ||||
| } | ||||
| 
 | ||||
| void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, | ||||
|                    Raster::Origin o, double gamma) | ||||
|                    Format fmt, double gamma) | ||||
| { | ||||
|     m_impl.reset(); | ||||
|     m_impl.reset(new Impl(r, pd, o, gamma)); | ||||
|     m_impl.reset(new Impl(r, pd, fmt, gamma)); | ||||
| } | ||||
| 
 | ||||
| void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, | ||||
|                    const std::array<bool, 2>& mirror, double gamma) | ||||
| { | ||||
|     m_impl.reset(); | ||||
|     m_impl.reset(new Impl(r, pd, mirror, gamma)); | ||||
| } | ||||
| 
 | ||||
| void Raster::reset() | ||||
|  | @ -208,13 +235,13 @@ void Raster::draw(const ClipperLib::Polygon &poly) | |||
|     m_impl->draw(poly); | ||||
| } | ||||
| 
 | ||||
| void Raster::save(std::ostream& stream, Compression comp) | ||||
| void Raster::save(std::ostream& stream, Format fmt) | ||||
| { | ||||
|     assert(m_impl); | ||||
|     if(!stream.good()) return; | ||||
| 
 | ||||
|     switch(comp) { | ||||
|     case Compression::PNG: { | ||||
|     switch(fmt) { | ||||
|     case Format::PNG: { | ||||
|         auto& b = m_impl->buffer(); | ||||
|         size_t out_len = 0; | ||||
|         void * rawdata = tdefl_write_image_to_png_file_in_memory( | ||||
|  | @ -231,7 +258,7 @@ void Raster::save(std::ostream& stream, Compression comp) | |||
| 
 | ||||
|         break; | ||||
|     } | ||||
|     case Compression::RAW: { | ||||
|     case Format::RAW: { | ||||
|         stream << "P5 " | ||||
|                << m_impl->resolution().width_px << " " | ||||
|                << m_impl->resolution().height_px << " " | ||||
|  | @ -244,14 +271,19 @@ void Raster::save(std::ostream& stream, Compression comp) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| RawBytes Raster::save(Raster::Compression comp) | ||||
| void Raster::save(std::ostream &stream) | ||||
| { | ||||
|     save(stream, m_impl->format()); | ||||
| } | ||||
| 
 | ||||
| RawBytes Raster::save(Format fmt) | ||||
| { | ||||
|     assert(m_impl); | ||||
| 
 | ||||
|     std::vector<std::uint8_t> data; size_t s = 0; | ||||
| 
 | ||||
|     switch(comp) { | ||||
|     case Compression::PNG: { | ||||
|     switch(fmt) { | ||||
|     case Format::PNG: { | ||||
|         void *rawdata = tdefl_write_image_to_png_file_in_memory( | ||||
|                     m_impl->buffer().data(), | ||||
|                     int(resolution().width_px), | ||||
|  | @ -265,7 +297,7 @@ RawBytes Raster::save(Raster::Compression comp) | |||
|         MZ_FREE(rawdata); | ||||
|         break; | ||||
|     } | ||||
|     case Compression::RAW: { | ||||
|     case Format::RAW: { | ||||
|         auto header = std::string("P5 ") + | ||||
|                 std::to_string(m_impl->resolution().width_px) + " " + | ||||
|                 std::to_string(m_impl->resolution().height_px) + " " + "255 "; | ||||
|  | @ -286,4 +318,12 @@ RawBytes Raster::save(Raster::Compression comp) | |||
|     return {std::move(data)}; | ||||
| } | ||||
| 
 | ||||
| RawBytes Raster::save() | ||||
| { | ||||
|     return save(m_impl->format()); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| #endif // SLARASTER_CPP
 | ||||
|  | @ -1,17 +1,21 @@ | |||
| #ifndef RASTERIZER_HPP | ||||
| #define RASTERIZER_HPP | ||||
| #ifndef SLARASTER_HPP | ||||
| #define SLARASTER_HPP | ||||
| 
 | ||||
| #include <ostream> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <array> | ||||
| #include <utility> | ||||
| #include <cstdint> | ||||
| 
 | ||||
| namespace ClipperLib { struct Polygon; } | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace Slic3r {  | ||||
| 
 | ||||
| class ExPolygon; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| // Raw byte buffer paired with its size. Suitable for compressed PNG data.
 | ||||
| class RawBytes { | ||||
| 
 | ||||
|  | @ -23,15 +27,18 @@ public: | |||
|      | ||||
|     size_t size() const { return m_buffer.size(); } | ||||
|     const uint8_t * data() { return m_buffer.data(); } | ||||
|      | ||||
|     RawBytes(const RawBytes&) = delete; | ||||
|     RawBytes& operator=(const RawBytes&) = delete; | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // FIXME: the following is needed for MSVC2013 compatibility
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     RawBytes(const RawBytes&) = delete; | ||||
|     RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} | ||||
|     // RawBytes(RawBytes&&) = default;
 | ||||
|     // RawBytes& operator=(RawBytes&&) = default;
 | ||||
| 
 | ||||
|     RawBytes& operator=(const RawBytes&) = delete; | ||||
|     RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} | ||||
|     RawBytes& operator=(RawBytes&& mv) { | ||||
|         m_buffer = std::move(mv.m_buffer); | ||||
|         return *this; | ||||
|  | @ -54,28 +61,19 @@ class Raster { | |||
| public: | ||||
| 
 | ||||
|     /// Supported compression types
 | ||||
|     enum class Compression { | ||||
|     enum class Format { | ||||
|         RAW,    //!> Uncompressed pixel data
 | ||||
|         PNG     //!> PNG compression
 | ||||
|     }; | ||||
| 
 | ||||
|     /// The Rasterizer expects the input polygons to have their coordinate
 | ||||
|     /// system origin in the bottom left corner. If the raster is then
 | ||||
|     /// configured with the TOP_LEFT origin parameter (in the constructor) than
 | ||||
|     /// it will flip the Y axis in output to maintain the correct orientation.
 | ||||
|     /// This is the default case with PNG images. They have the origin in the
 | ||||
|     /// top left corner. Without the flipping, the image would be upside down
 | ||||
|     /// with the scaled (clipper) coordinate system of the input polygons.
 | ||||
|     enum class Origin { | ||||
|         TOP_LEFT, | ||||
|         BOTTOM_LEFT | ||||
|     }; | ||||
| 
 | ||||
|     /// Type that represents a resolution in pixels.
 | ||||
|     struct Resolution { | ||||
|         unsigned width_px; | ||||
|         unsigned height_px; | ||||
|         inline Resolution(unsigned w, unsigned h): width_px(w), height_px(h) {} | ||||
| 
 | ||||
|         inline Resolution(unsigned w = 0, unsigned h = 0): | ||||
|             width_px(w), height_px(h) {} | ||||
| 
 | ||||
|         inline unsigned pixels() const /*noexcept*/ { | ||||
|             return width_px * height_px; | ||||
|         } | ||||
|  | @ -85,24 +83,34 @@ public: | |||
|     struct PixelDim { | ||||
|         double w_mm; | ||||
|         double h_mm; | ||||
|         inline PixelDim(double px_width_mm, double px_height_mm ): | ||||
|         inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0): | ||||
|             w_mm(px_width_mm), h_mm(px_height_mm) {} | ||||
|     }; | ||||
| 
 | ||||
|     /// Constructor taking the resolution and the pixel dimension.
 | ||||
|     Raster(const Resolution& r,  const PixelDim& pd,  | ||||
|            Origin o = Origin::BOTTOM_LEFT, double gamma = 1.0); | ||||
|     template <class...Args> Raster(Args...args) {  | ||||
|         reset(std::forward<Args>(args)...);  | ||||
|     } | ||||
|      | ||||
|     Raster(); | ||||
|     Raster(const Raster& cpy) = delete; | ||||
|     Raster& operator=(const Raster& cpy) = delete; | ||||
|     Raster(Raster&& m); | ||||
|     Raster& operator=(Raster&&); | ||||
|     ~Raster(); | ||||
| 
 | ||||
|     /// Reallocated everything for the given resolution and pixel dimension.
 | ||||
|     void reset(const Resolution& r, const PixelDim& pd, double gamma = 1.0); | ||||
|     void reset(const Resolution& r, const PixelDim& pd, Origin o, double gamma); | ||||
| 
 | ||||
|     /// The third parameter is either the X, Y mirroring or a supported format 
 | ||||
|     /// for which the correct mirroring will be configured.
 | ||||
|     void reset(const Resolution&,  | ||||
|                const PixelDim&,  | ||||
|                const std::array<bool, 2>& mirror,  | ||||
|                double gamma = 1.0); | ||||
|      | ||||
|     void reset(const Resolution& r,  | ||||
|                const PixelDim& pd,  | ||||
|                Format o,  | ||||
|                double gamma = 1.0); | ||||
|      | ||||
|     /**
 | ||||
|      * Release the allocated resources. Drawing in this state ends in | ||||
|      * unspecified behavior. | ||||
|  | @ -119,11 +127,24 @@ public: | |||
|     void draw(const ExPolygon& poly); | ||||
|     void draw(const ClipperLib::Polygon& poly); | ||||
| 
 | ||||
|     // Saving the raster: 
 | ||||
|     // It is possible to override the format given in the constructor but
 | ||||
|     // be aware that the mirroring will not be modified.
 | ||||
|      | ||||
|     /// Save the raster on the specified stream.
 | ||||
|     void save(std::ostream& stream, Compression comp = Compression::RAW); | ||||
|     void save(std::ostream& stream, Format); | ||||
|     void save(std::ostream& stream); | ||||
| 
 | ||||
|     RawBytes save(Compression comp = Compression::RAW); | ||||
|     /// Save into a continuous byte stream which is returned.
 | ||||
|     RawBytes save(Format fmt); | ||||
|     RawBytes save(); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| #endif // RASTERIZER_HPP
 | ||||
| // This prevents the duplicate default constructor warning on MSVC2013
 | ||||
| template<> Raster::Raster(); | ||||
| 
 | ||||
| 
 | ||||
| } // sla
 | ||||
| } // Slic3r
 | ||||
| 
 | ||||
| #endif // SLARASTER_HPP
 | ||||
							
								
								
									
										136
									
								
								src/libslic3r/SLA/SLARasterWriter.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/libslic3r/SLA/SLARasterWriter.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,136 @@ | |||
| #include "SLARasterWriter.hpp" | ||||
| #include "libslic3r/Zipper.hpp" | ||||
| #include "ExPolygon.hpp" | ||||
| #include <libnest2d/backends/clipper/clipper_polygon.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| std::string SLARasterWriter::createIniContent(const std::string& projectname) const  | ||||
| { | ||||
|     auto expt_str = std::to_string(m_exp_time_s); | ||||
|     auto expt_first_str = std::to_string(m_exp_time_first_s); | ||||
|     auto layerh_str = std::to_string(m_layer_height); | ||||
| 
 | ||||
|     const std::string cnt_fade_layers = std::to_string(m_cnt_fade_layers); | ||||
|     const std::string cnt_slow_layers = std::to_string(m_cnt_slow_layers); | ||||
|     const std::string cnt_fast_layers = std::to_string(m_cnt_fast_layers); | ||||
|     const std::string used_material   = std::to_string(m_used_material); | ||||
| 
 | ||||
|     return std::string( | ||||
|     "action = print\n" | ||||
|     "jobDir = ") + projectname + "\n" + | ||||
|     "expTime = " + expt_str + "\n" | ||||
|     "expTimeFirst = " + expt_first_str + "\n" | ||||
|     "numFade = " + cnt_fade_layers + "\n" | ||||
|     "layerHeight = " + layerh_str + "\n" | ||||
|     "usedMaterial = " + used_material + "\n" | ||||
|     "numSlow = " + cnt_slow_layers + "\n" | ||||
|                                      "numFast = " + cnt_fast_layers + "\n"; | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::flpXY(ClipperLib::Polygon &poly) | ||||
| { | ||||
|     for(auto& p : poly.Contour) std::swap(p.X, p.Y); | ||||
|     std::reverse(poly.Contour.begin(), poly.Contour.end()); | ||||
|      | ||||
|     for(auto& h : poly.Holes) { | ||||
|         for(auto& p : h) std::swap(p.X, p.Y); | ||||
|         std::reverse(h.begin(), h.end()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::flpXY(ExPolygon &poly) | ||||
| { | ||||
|     for(auto& p : poly.contour.points) p = Point(p.y(), p.x()); | ||||
|     std::reverse(poly.contour.points.begin(), poly.contour.points.end()); | ||||
|      | ||||
|     for(auto& h : poly.holes) { | ||||
|         for(auto& p : h.points) p = Point(p.y(), p.x()); | ||||
|         std::reverse(h.points.begin(), h.points.end()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SLARasterWriter::SLARasterWriter(const SLAPrinterConfig &cfg,  | ||||
|                                  const SLAMaterialConfig &mcfg,  | ||||
|                                  double layer_height) | ||||
| { | ||||
|     double w = cfg.display_width.getFloat(); | ||||
|     double h = cfg.display_height.getFloat(); | ||||
|     auto pw = unsigned(cfg.display_pixels_x.getInt()); | ||||
|     auto ph = unsigned(cfg.display_pixels_y.getInt()); | ||||
|      | ||||
|     m_mirror[X] = cfg.display_mirror_x.getBool(); | ||||
|      | ||||
|     // PNG raster will implicitly do an Y mirror
 | ||||
|     m_mirror[Y] = ! cfg.display_mirror_y.getBool(); | ||||
|          | ||||
|     auto ro = cfg.display_orientation.getInt(); | ||||
|      | ||||
|     if(ro == roPortrait) { | ||||
|         std::swap(w, h); | ||||
|         std::swap(pw, ph); | ||||
|         m_o = roPortrait; | ||||
|          | ||||
|         // XY flipping implicitly does an X mirror
 | ||||
|         m_mirror[X] = ! m_mirror[X]; | ||||
|     } else m_o = roLandscape; | ||||
|      | ||||
|     m_res = Raster::Resolution(pw, ph); | ||||
|     m_pxdim = Raster::PixelDim(w/pw, h/ph); | ||||
|     m_exp_time_s = mcfg.exposure_time.getFloat(); | ||||
|     m_exp_time_first_s = mcfg.initial_exposure_time.getFloat(); | ||||
|     m_layer_height = layer_height; | ||||
|      | ||||
|     m_gamma = cfg.gamma_correction.getFloat(); | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::save(const std::string &fpath, const std::string &prjname) | ||||
| { | ||||
|     try { | ||||
|         Zipper zipper(fpath); // zipper with no compression
 | ||||
|          | ||||
|         std::string project = prjname.empty()? | ||||
|                     boost::filesystem::path(fpath).stem().string() : prjname; | ||||
|          | ||||
|         zipper.add_entry("config.ini"); | ||||
|          | ||||
|         zipper << createIniContent(project); | ||||
|          | ||||
|         for(unsigned i = 0; i < m_layers_rst.size(); i++) | ||||
|         { | ||||
|             if(m_layers_rst[i].rawbytes.size() > 0) { | ||||
|                 char lyrnum[6]; | ||||
|                 std::sprintf(lyrnum, "%.5d", i); | ||||
|                 auto zfilename = project + lyrnum + ".png"; | ||||
|                  | ||||
|                 // Add binary entry to the zipper
 | ||||
|                 zipper.add_entry(zfilename, | ||||
|                                  m_layers_rst[i].rawbytes.data(), | ||||
|                                  m_layers_rst[i].rawbytes.size()); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         zipper.finalize(); | ||||
|     } catch(std::exception& e) { | ||||
|         BOOST_LOG_TRIVIAL(error) << e.what(); | ||||
|         // Rethrow the exception
 | ||||
|         throw; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::set_statistics(const std::vector<double> statistics) | ||||
| { | ||||
|     if (statistics.size() != psCnt) | ||||
|         return; | ||||
|      | ||||
|     m_used_material   = statistics[psUsedMaterial]; | ||||
|     m_cnt_fade_layers = int(statistics[psNumFade]); | ||||
|     m_cnt_slow_layers = int(statistics[psNumSlow]); | ||||
|     m_cnt_fast_layers = int(statistics[psNumFast]); | ||||
| } | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
							
								
								
									
										167
									
								
								src/libslic3r/SLA/SLARasterWriter.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/libslic3r/SLA/SLARasterWriter.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,167 @@ | |||
| #ifndef SLARASTERWRITER_HPP | ||||
| #define SLARASTERWRITER_HPP | ||||
| 
 | ||||
| // For png export of the sliced model
 | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| #include <vector> | ||||
| #include <array> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| 
 | ||||
| #include "SLARaster.hpp" | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| // Implementation for PNG raster output
 | ||||
| // Be aware that if a large number of layers are allocated, it can very well
 | ||||
| // exhaust the available memory especially on 32 bit platform.
 | ||||
| // This class is designed to be used in parallel mode. Layers have an ID and
 | ||||
| // each layer can be written and compressed independently (in parallel).
 | ||||
| // At the end when all layers where written, the save method can be used to 
 | ||||
| // write out the result into a zipped archive.
 | ||||
| class SLARasterWriter | ||||
| { | ||||
| public: | ||||
|     enum RasterOrientation { | ||||
|         roLandscape, | ||||
|         roPortrait | ||||
|     }; | ||||
|      | ||||
|     // Used for addressing parameters of set_statistics()
 | ||||
|     enum ePrintStatistics | ||||
|     { | ||||
|         psUsedMaterial = 0, | ||||
|         psNumFade, | ||||
|         psNumSlow, | ||||
|         psNumFast, | ||||
|      | ||||
|         psCnt | ||||
|     }; | ||||
|      | ||||
| private: | ||||
|      | ||||
|     // A struct to bind the raster image data and its compressed bytes together.
 | ||||
|     struct Layer { | ||||
|         Raster raster; | ||||
|         RawBytes rawbytes; | ||||
| 
 | ||||
|         Layer() = default; | ||||
|         Layer(const Layer&) = delete; // The image is big, do not copy by accident
 | ||||
|         Layer& operator=(const Layer&) = delete; | ||||
|          | ||||
|         // /////////////////////////////////////////////////////////////////////
 | ||||
|         // FIXME: the following is needed for MSVC2013 compatibility
 | ||||
|         // /////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|         // Layer(Layer&& m) = default;
 | ||||
|         // Layer& operator=(Layer&&) = default;
 | ||||
|         Layer(Layer &&m): | ||||
|             raster(std::move(m.raster)), rawbytes(std::move(m.rawbytes)) {} | ||||
|         Layer& operator=(Layer &&m) { | ||||
|             raster = std::move(m.raster); rawbytes = std::move(m.rawbytes); | ||||
|             return *this; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // We will save the compressed PNG data into RawBytes type buffers in 
 | ||||
|     // parallel. Later we can write every layer to the disk sequentially.
 | ||||
|     std::vector<Layer> m_layers_rst; | ||||
|     Raster::Resolution m_res; | ||||
|     Raster::PixelDim m_pxdim; | ||||
|     double m_exp_time_s = .0, m_exp_time_first_s = .0; | ||||
|     double m_layer_height = .0; | ||||
|     RasterOrientation m_o = roPortrait; | ||||
|     std::array<bool, 2> m_mirror; | ||||
|      | ||||
|     double m_gamma; | ||||
| 
 | ||||
|     double m_used_material = 0.0; | ||||
|     int    m_cnt_fade_layers = 0; | ||||
|     int    m_cnt_slow_layers = 0; | ||||
|     int    m_cnt_fast_layers = 0; | ||||
| 
 | ||||
|     std::string createIniContent(const std::string& projectname) const; | ||||
|      | ||||
|     static void flpXY(ClipperLib::Polygon& poly); | ||||
|     static void flpXY(ExPolygon& poly); | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     SLARasterWriter(const SLAPrinterConfig& cfg,  | ||||
|                     const SLAMaterialConfig& mcfg,  | ||||
|                     double layer_height); | ||||
| 
 | ||||
|     SLARasterWriter(const SLARasterWriter& ) = delete; | ||||
|     SLARasterWriter& operator=(const SLARasterWriter&) = delete; | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // FIXME: the following is needed for MSVC2013 compatibility
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     // SLARasterWriter(SLARasterWriter&& m) = default;
 | ||||
|     // SLARasterWriter& operator=(SLARasterWriter&&) = default;
 | ||||
|     SLARasterWriter(SLARasterWriter&& m): | ||||
|         m_layers_rst(std::move(m.m_layers_rst)), | ||||
|         m_res(m.m_res), | ||||
|         m_pxdim(m.m_pxdim), | ||||
|         m_exp_time_s(m.m_exp_time_s), | ||||
|         m_exp_time_first_s(m.m_exp_time_first_s), | ||||
|         m_layer_height(m.m_layer_height), | ||||
|         m_o(m.m_o), | ||||
|         m_mirror(std::move(m.m_mirror)), | ||||
|         m_gamma(m.m_gamma), | ||||
|         m_used_material(m.m_used_material), | ||||
|         m_cnt_fade_layers(m.m_cnt_fade_layers), | ||||
|         m_cnt_slow_layers(m.m_cnt_slow_layers), | ||||
|         m_cnt_fast_layers(m.m_cnt_fast_layers) | ||||
|     {} | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } | ||||
|     inline unsigned layers() const { return unsigned(m_layers_rst.size()); } | ||||
|      | ||||
|     template<class Poly> void draw_polygon(const Poly& p, unsigned lyr) { | ||||
|         assert(lyr < m_layers_rst.size()); | ||||
|         if(m_o == roPortrait) { | ||||
|             Poly poly(p); flpXY(poly); | ||||
|             m_layers_rst[lyr].raster.draw(poly); | ||||
|         } | ||||
|         else m_layers_rst[lyr].raster.draw(p); | ||||
|     } | ||||
| 
 | ||||
|     inline void begin_layer(unsigned lyr) { | ||||
|         if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); | ||||
|         m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma); | ||||
|     } | ||||
| 
 | ||||
|     inline void begin_layer() { | ||||
|         m_layers_rst.emplace_back(); | ||||
|         m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma); | ||||
|     } | ||||
| 
 | ||||
|     inline void finish_layer(unsigned lyr_id) { | ||||
|         assert(lyr_id < m_layers_rst.size()); | ||||
|         m_layers_rst[lyr_id].rawbytes = | ||||
|                 m_layers_rst[lyr_id].raster.save(Raster::Format::PNG); | ||||
|         m_layers_rst[lyr_id].raster.reset(); | ||||
|     } | ||||
| 
 | ||||
|     inline void finish_layer() { | ||||
|         if(!m_layers_rst.empty()) { | ||||
|             m_layers_rst.back().rawbytes = | ||||
|                     m_layers_rst.back().raster.save(Raster::Format::PNG); | ||||
|             m_layers_rst.back().raster.reset(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void save(const std::string& fpath, const std::string& prjname = ""); | ||||
| 
 | ||||
|     void set_statistics(const std::vector<double> statistics); | ||||
| }; | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // SLARASTERWRITER_HPP
 | ||||
|  | @ -44,7 +44,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj, | |||
|     // call the status callback in each iteration but the actual value may be
 | ||||
|     // the same for subsequent iterations (status goes from 0 to 100 but
 | ||||
|     // iterations can be many more)
 | ||||
|     auto objfunc = [&emesh, &status, &statuscb, max_tries] | ||||
|     auto objfunc = [&emesh, &status, &statuscb, &stopcond, max_tries] | ||||
|             (double rx, double ry, double rz) | ||||
|     { | ||||
|         EigenMesh3D& m = emesh; | ||||
|  | @ -91,7 +91,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj, | |||
|         } | ||||
| 
 | ||||
|         // report status
 | ||||
|         statuscb( unsigned(++status * 100.0/max_tries) ); | ||||
|         if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); | ||||
| 
 | ||||
|         return score; | ||||
|     }; | ||||
|  |  | |||
|  | @ -236,13 +236,13 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0}) | |||
|     // According to the slicing algorithms, we need to aid them with generating
 | ||||
|     // a watertight body. So we create a triangle fan for the upper and lower
 | ||||
|     // ending of the cylinder to close the geometry.
 | ||||
|     points.emplace_back(jp); size_t ci = points.size() - 1; | ||||
|     points.emplace_back(jp); int ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(i + offs + 1, i + offs, ci); | ||||
| 
 | ||||
|     indices.emplace_back(offs, steps + offs - 1, ci); | ||||
| 
 | ||||
|     points.emplace_back(endp); ci = points.size() - 1; | ||||
|     points.emplace_back(endp); ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(ci, i, i + 1); | ||||
| 
 | ||||
|  |  | |||
|  | @ -121,19 +121,10 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { | |||
|     V.resize(3*stl.stats.number_of_facets, 3); | ||||
|     F.resize(stl.stats.number_of_facets, 3); | ||||
|     for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { | ||||
|         const stl_facet* facet = stl.facet_start+i; | ||||
|         V(3*i+0, 0) = double(facet->vertex[0](0)); | ||||
|         V(3*i+0, 1) = double(facet->vertex[0](1)); | ||||
|         V(3*i+0, 2) = double(facet->vertex[0](2)); | ||||
| 
 | ||||
|         V(3*i+1, 0) = double(facet->vertex[1](0)); | ||||
|         V(3*i+1, 1) = double(facet->vertex[1](1)); | ||||
|         V(3*i+1, 2) = double(facet->vertex[1](2)); | ||||
| 
 | ||||
|         V(3*i+2, 0) = double(facet->vertex[2](0)); | ||||
|         V(3*i+2, 1) = double(facet->vertex[2](1)); | ||||
|         V(3*i+2, 2) = double(facet->vertex[2](2)); | ||||
| 
 | ||||
|         const stl_facet &facet = stl.facet_start[i]; | ||||
| 		V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast<double>(); | ||||
| 		V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast<double>(); | ||||
| 		V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast<double>(); | ||||
|         F(i, 0) = int(3*i+0); | ||||
|         F(i, 1) = int(3*i+1); | ||||
|         F(i, 2) = int(3*i+2); | ||||
|  |  | |||
|  | @ -28,14 +28,16 @@ namespace Slic3r { | |||
| 
 | ||||
| using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>; | ||||
| 
 | ||||
| class SLAPrintObject::SupportData { | ||||
| class SLAPrintObject::SupportData | ||||
| { | ||||
| public: | ||||
|     sla::EigenMesh3D emesh;              // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> support_points;     // all the support points (manual/auto)
 | ||||
|     SupportTreePtr   support_tree_ptr;   // the supports
 | ||||
|     SlicedSupports   support_slices;     // sliced supports
 | ||||
|     sla::EigenMesh3D emesh;             // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> | ||||
|                    support_points;      // all the support points (manual/auto)
 | ||||
|     SupportTreePtr support_tree_ptr;    // the supports
 | ||||
|     SlicedSupports support_slices;      // sliced supports
 | ||||
| 
 | ||||
|     inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} | ||||
|     inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} | ||||
| }; | ||||
| 
 | ||||
| namespace { | ||||
|  | @ -666,11 +668,11 @@ void SLAPrint::process() | |||
|     double ilhd = m_material_config.initial_layer_height.getFloat(); | ||||
|     auto   ilh  = float(ilhd); | ||||
| 
 | ||||
|     auto ilhs = coord_t(ilhd / SCALING_FACTOR); | ||||
|     auto ilhs = scaled(ilhd); | ||||
|     const size_t objcount = m_objects.size(); | ||||
| 
 | ||||
|     const unsigned min_objstatus = 0;   // where the per object operations start
 | ||||
|     const unsigned max_objstatus = 50;  // where the per object operations end
 | ||||
|     static const unsigned min_objstatus = 0;   // where the per object operations start
 | ||||
|     static const unsigned max_objstatus = 50;  // where the per object operations end
 | ||||
| 
 | ||||
|     // the coefficient that multiplies the per object status values which
 | ||||
|     // are set up for <0, 100>. They need to be scaled into the whole process
 | ||||
|  | @ -687,31 +689,32 @@ void SLAPrint::process() | |||
| 
 | ||||
|     // Slicing the model object. This method is oversimplified and needs to
 | ||||
|     // be compared with the fff slicing algorithm for verification
 | ||||
|     auto slice_model = [this, ilhs, ilh, ilhd](SLAPrintObject& po) { | ||||
|     auto slice_model = [this, ilhs, ilh](SLAPrintObject& po) { | ||||
|         const TriangleMesh& mesh = po.transformed_mesh(); | ||||
| 
 | ||||
|         // We need to prepare the slice index...
 | ||||
| 
 | ||||
|         double lhd  = m_objects.front()->m_config.layer_height.getFloat(); | ||||
|         float  lh   = float(lhd); | ||||
|         auto   lhs  = coord_t(lhd  / SCALING_FACTOR); | ||||
|         auto   lhs  = scaled(lhd); | ||||
| 
 | ||||
|         auto&& bb3d = mesh.bounding_box(); | ||||
|         double minZ = bb3d.min(Z) - po.get_elevation(); | ||||
|         double maxZ = bb3d.max(Z); | ||||
|         auto &&bb3d  = mesh.bounding_box(); | ||||
|         double minZ  = bb3d.min(Z) - po.get_elevation(); | ||||
|         double maxZ  = bb3d.max(Z); | ||||
|         auto   minZf = float(minZ); | ||||
| 
 | ||||
|         auto minZs = coord_t(minZ / SCALING_FACTOR); | ||||
|         auto maxZs = coord_t(maxZ / SCALING_FACTOR); | ||||
|         auto minZs = scaled(minZ); | ||||
|         auto maxZs = scaled(maxZ); | ||||
| 
 | ||||
|         po.m_slice_index.clear(); | ||||
|          | ||||
|         size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); | ||||
|         po.m_slice_index.reserve(cap); | ||||
|          | ||||
|         po.m_slice_index.emplace_back(minZs + ilhs, minZ + ilhd / 2.0, ilh); | ||||
|         po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); | ||||
| 
 | ||||
|         for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)  | ||||
|             po.m_slice_index.emplace_back(h, h*SCALING_FACTOR - lhd / 2.0, lh); | ||||
|         for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) | ||||
|             po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh); | ||||
|         | ||||
|         // Just get the first record that is form the model:
 | ||||
|         auto slindex_it = | ||||
|  | @ -737,7 +740,7 @@ void SLAPrint::process() | |||
| 
 | ||||
|         auto mit = slindex_it; | ||||
|         double doffs = m_printer_config.absolute_correction.getFloat(); | ||||
|         coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR); | ||||
|         coord_t clpr_offs = scaled(doffs); | ||||
|         for(size_t id = 0; | ||||
|             id < po.m_model_slices.size() && mit != po.m_slice_index.end(); | ||||
|             id++) | ||||
|  | @ -745,7 +748,7 @@ void SLAPrint::process() | |||
|             // We apply the printer correction offset here.
 | ||||
|             if(clpr_offs != 0) | ||||
|                 po.m_model_slices[id] =  | ||||
|                         offset_ex(po.m_model_slices[id], clpr_offs); | ||||
|                         offset_ex(po.m_model_slices[id], float(clpr_offs)); | ||||
|              | ||||
|             mit->set_model_slice_idx(po, id); ++mit; | ||||
|         } | ||||
|  | @ -949,15 +952,15 @@ void SLAPrint::process() | |||
|         } | ||||
| 
 | ||||
|         double doffs = m_printer_config.absolute_correction.getFloat(); | ||||
|         coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR); | ||||
|         coord_t clpr_offs = scaled(doffs); | ||||
|         for(size_t i = 0; | ||||
|             i < sd->support_slices.size() && i < po.m_slice_index.size(); | ||||
|             ++i) | ||||
|         { | ||||
|             // We apply the printer correction offset here.
 | ||||
|             if(clpr_offs != 0) | ||||
|                 sd->support_slices[i] =  | ||||
|                         offset_ex(sd->support_slices[i], clpr_offs); | ||||
|                 sd->support_slices[i] = | ||||
|                     offset_ex(sd->support_slices[i], float(clpr_offs)); | ||||
|              | ||||
|             po.m_slice_index[i].set_support_slice_idx(po, i); | ||||
|         } | ||||
|  | @ -1011,7 +1014,7 @@ void SLAPrint::process() | |||
|         namespace sl = libnest2d::shapelike;    // For algorithms
 | ||||
| 
 | ||||
|         // If the raster has vertical orientation, we will flip the coordinates
 | ||||
|         bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; | ||||
| //        bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
 | ||||
| 
 | ||||
|         // Set up custom union and diff functions for clipper polygons
 | ||||
|         auto polyunion = [] (const ClipperPolygons& subjects) | ||||
|  | @ -1063,15 +1066,15 @@ void SLAPrint::process() | |||
| 
 | ||||
|         const int    fade_layers_cnt    = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
 | ||||
| 
 | ||||
|         const double width              = m_printer_config.display_width.getFloat() / SCALING_FACTOR; | ||||
|         const double height             = m_printer_config.display_height.getFloat() / SCALING_FACTOR; | ||||
|         const double width              = scaled(m_printer_config.display_width.getFloat()); | ||||
|         const double height             = scaled(m_printer_config.display_height.getFloat()); | ||||
|         const double display_area       = width*height; | ||||
| 
 | ||||
|         // get polygons for all instances in the object
 | ||||
|         auto get_all_polygons = | ||||
|                 [flpXY](const ExPolygons& input_polygons, | ||||
|                         const std::vector<SLAPrintObject::Instance>& instances, | ||||
|                         bool is_lefthanded) | ||||
|                 [](const ExPolygons& input_polygons, | ||||
|                    const std::vector<SLAPrintObject::Instance>& instances, | ||||
|                    bool is_lefthanded) | ||||
|         { | ||||
|             ClipperPolygons polygons; | ||||
|             polygons.reserve(input_polygons.size() * instances.size()); | ||||
|  | @ -1085,7 +1088,7 @@ void SLAPrint::process() | |||
| 
 | ||||
|                     // We need to reverse if flpXY OR is_lefthanded is true but
 | ||||
|                     // not if both are true which is a logical inequality (XOR)
 | ||||
|                     bool needreverse = flpXY != is_lefthanded; | ||||
|                     bool needreverse = /*flpXY !=*/ is_lefthanded; | ||||
| 
 | ||||
|                     // should be a move
 | ||||
|                     poly.Contour.reserve(polygon.contour.size() + 1); | ||||
|  | @ -1120,10 +1123,10 @@ void SLAPrint::process() | |||
|                     sl::translate(poly, ClipperPoint{instances[i].shift(X), | ||||
|                                                      instances[i].shift(Y)}); | ||||
| 
 | ||||
|                     if (flpXY) { | ||||
|                         for(auto& p : poly.Contour) std::swap(p.X, p.Y); | ||||
|                         for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); | ||||
|                     } | ||||
| //                    if (flpXY) {
 | ||||
| //                        for(auto& p : poly.Contour) std::swap(p.X, p.Y);
 | ||||
| //                        for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y);
 | ||||
| //                    }
 | ||||
| 
 | ||||
|                     polygons.emplace_back(std::move(poly)); | ||||
|                 } | ||||
|  | @ -1170,13 +1173,20 @@ void SLAPrint::process() | |||
|             ClipperPolygons model_polygons; | ||||
|             ClipperPolygons supports_polygons; | ||||
| 
 | ||||
|             size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { | ||||
|                                            return a + sr.get_slice(soModel).size(); | ||||
|             size_t c = std::accumulate(layer.slices().begin(), | ||||
|                                        layer.slices().end(), | ||||
|                                        size_t(0), | ||||
|                                        [](size_t a, const SliceRecord &sr) { | ||||
|                                            return a + sr.get_slice(soModel) | ||||
|                                                         .size(); | ||||
|                                        }); | ||||
| 
 | ||||
|             model_polygons.reserve(c); | ||||
| 
 | ||||
|             c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { | ||||
|             c = std::accumulate(layer.slices().begin(), | ||||
|                                 layer.slices().end(), | ||||
|                                 size_t(0), | ||||
|                                 [](size_t a, const SliceRecord &sr) { | ||||
|                                     return a + sr.get_slice(soModel).size(); | ||||
|                                 }); | ||||
| 
 | ||||
|  | @ -1264,8 +1274,9 @@ void SLAPrint::process() | |||
|         // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
 | ||||
|         tbb::parallel_for<size_t, decltype(printlayerfn)>(0, m_printer_input.size(), printlayerfn); | ||||
| 
 | ||||
|         m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; | ||||
|         m_print_statistics.objects_used_material = models_volume  * SCALING_FACTOR * SCALING_FACTOR; | ||||
|         auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; | ||||
|         m_print_statistics.support_used_material = supports_volume * SCALING2; | ||||
|         m_print_statistics.objects_used_material = models_volume  * SCALING2; | ||||
| 
 | ||||
|         // Estimated printing time
 | ||||
|         // A layers count o the highest object
 | ||||
|  | @ -1281,38 +1292,14 @@ void SLAPrint::process() | |||
|     }; | ||||
| 
 | ||||
|     // Rasterizing the model objects, and their supports
 | ||||
|     auto rasterize = [this, max_objstatus]() { | ||||
|     auto rasterize = [this]() { | ||||
|         if(canceled()) return; | ||||
| 
 | ||||
|         // collect all the keys
 | ||||
| 
 | ||||
|         // If the raster has vertical orientation, we will flip the coordinates
 | ||||
|         bool flpXY = m_printer_config.display_orientation.getInt() == | ||||
|                 SLADisplayOrientation::sladoPortrait; | ||||
| 
 | ||||
|         { // create a raster printer for the current print parameters
 | ||||
|             // I don't know any better
 | ||||
|             auto& ocfg = m_objects.front()->m_config; | ||||
|             auto& matcfg = m_material_config; | ||||
|             auto& printcfg = m_printer_config; | ||||
| 
 | ||||
|             double w = printcfg.display_width.getFloat(); | ||||
|             double h = printcfg.display_height.getFloat(); | ||||
|             auto pw = unsigned(printcfg.display_pixels_x.getInt()); | ||||
|             auto ph = unsigned(printcfg.display_pixels_y.getInt()); | ||||
|             double lh = ocfg.layer_height.getFloat(); | ||||
|             double exp_t = matcfg.exposure_time.getFloat(); | ||||
|             double iexp_t = matcfg.initial_exposure_time.getFloat(); | ||||
|              | ||||
|             double gamma = m_printer_config.gamma_correction.getFloat(); | ||||
| 
 | ||||
|             if(flpXY) { std::swap(w, h); std::swap(pw, ph); } | ||||
| 
 | ||||
|             m_printer.reset( | ||||
|                 new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, | ||||
|                                flpXY? SLAPrinter::RO_PORTRAIT :  | ||||
|                                       SLAPrinter::RO_LANDSCAPE,  | ||||
|                                gamma)); | ||||
|             double layerh = m_default_object_config.layer_height.getFloat(); | ||||
|             m_printer.reset(new SLAPrinter(m_printer_config,  | ||||
|                                            m_material_config,  | ||||
|                                            layerh)); | ||||
|         } | ||||
| 
 | ||||
|         // Allocate space for all the layers
 | ||||
|  | @ -1376,11 +1363,12 @@ void SLAPrint::process() | |||
|         tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn); | ||||
| 
 | ||||
|         // Set statistics values to the printer
 | ||||
|         m_printer->set_statistics({(m_print_statistics.objects_used_material + m_print_statistics.support_used_material)/1000, | ||||
|                                 double(m_default_object_config.faded_layers.getInt()), | ||||
|                                 double(m_print_statistics.slow_layers_count), | ||||
|                                 double(m_print_statistics.fast_layers_count) | ||||
|                                 }); | ||||
|         m_printer->set_statistics( | ||||
|             {(m_print_statistics.objects_used_material | ||||
|               + m_print_statistics.support_used_material) / 1000, | ||||
|              double(m_default_object_config.faded_layers.getInt()), | ||||
|              double(m_print_statistics.slow_layers_count), | ||||
|              double(m_print_statistics.fast_layers_count)}); | ||||
|     }; | ||||
| 
 | ||||
|     using slaposFn = std::function<void(SLAPrintObject&)>; | ||||
|  | @ -1408,25 +1396,36 @@ void SLAPrint::process() | |||
| 
 | ||||
|     // TODO: this loop could run in parallel but should not exhaust all the CPU
 | ||||
|     // power available
 | ||||
|     // Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects.
 | ||||
|     std::vector<SLAPrintObjectStep> step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount }; | ||||
|     for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) { | ||||
|         for(SLAPrintObject * po : m_objects) { | ||||
|     // Calculate the support structures first before slicing the supports,
 | ||||
|     // so that the preview will get displayed ASAP for all objects.
 | ||||
|     std::vector<SLAPrintObjectStep> step_ranges = {slaposObjectSlice, | ||||
|                                                    slaposSliceSupports, | ||||
|                                                    slaposCount}; | ||||
| 
 | ||||
|             BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; | ||||
|     for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++idx_range) { | ||||
|         for (SLAPrintObject *po : m_objects) { | ||||
| 
 | ||||
|             for (int s = int(step_ranges[idx_range]); s < int(step_ranges[idx_range + 1]); ++s) { | ||||
|             BOOST_LOG_TRIVIAL(info) | ||||
|                 << "Slicing object " << po->model_object()->name; | ||||
| 
 | ||||
|             for (int s = int(step_ranges[idx_range]); | ||||
|                  s < int(step_ranges[idx_range + 1]); | ||||
|                  ++s) { | ||||
|                 auto currentstep = static_cast<SLAPrintObjectStep>(s); | ||||
| 
 | ||||
|                 // Cancellation checking. Each step will check for cancellation
 | ||||
|                 // on its own and return earlier gracefully. Just after it returns
 | ||||
|                 // execution gets to this point and throws the canceled signal.
 | ||||
|                 // Cancellation checking. Each step will check for
 | ||||
|                 // cancellation on its own and return earlier gracefully.
 | ||||
|                 // Just after it returns execution gets to this point and
 | ||||
|                 // throws the canceled signal.
 | ||||
|                 throw_if_canceled(); | ||||
| 
 | ||||
|                 st += incr * ostepd; | ||||
| 
 | ||||
|                 if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { | ||||
|                     m_report_status(*this, st, OBJ_STEP_LABELS(currentstep)); | ||||
|                 if (po->m_stepmask[currentstep] | ||||
|                     && po->set_started(currentstep)) { | ||||
|                     m_report_status(*this, | ||||
|                                     st, | ||||
|                                     OBJ_STEP_LABELS(currentstep)); | ||||
|                     pobj_program[currentstep](*po); | ||||
|                     throw_if_canceled(); | ||||
|                     po->set_done(currentstep); | ||||
|  | @ -1488,6 +1487,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt | |||
|         "display_height", | ||||
|         "display_pixels_x", | ||||
|         "display_pixels_y", | ||||
|         "display_mirror_x", | ||||
|         "display_mirror_y", | ||||
|         "display_orientation" | ||||
|     }; | ||||
| 
 | ||||
|  | @ -1786,8 +1787,8 @@ std::vector<sla::SupportPoint> SLAPrintObject::transformed_support_points() cons | |||
|     ret.reserve(spts.size()); | ||||
| 
 | ||||
|     for(sla::SupportPoint& sp : spts) { | ||||
|         Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); | ||||
|         ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); | ||||
|         Vec3f transformed_pos = trafo().cast<float>() * sp.pos; | ||||
|         ret.emplace_back(transformed_pos, sp.head_front_radius, sp.is_new_island); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|  |  | |||
|  | @ -3,11 +3,11 @@ | |||
| 
 | ||||
| #include <mutex> | ||||
| #include "PrintBase.hpp" | ||||
| #include "PrintExport.hpp" | ||||
| //#include "PrintExport.hpp"
 | ||||
| #include "SLA/SLARasterWriter.hpp" | ||||
| #include "Point.hpp" | ||||
| #include "MTUtils.hpp" | ||||
| #include <libnest2d/backends/clipper/clipper_polygon.hpp> | ||||
| #include "Zipper.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -54,15 +54,15 @@ public: | |||
|     bool                        is_left_handed() const { return m_left_handed; } | ||||
| 
 | ||||
|     struct Instance { | ||||
|     	Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} | ||||
| 		bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } | ||||
|     	// ID of the corresponding ModelInstance.
 | ||||
| 		ModelID instance_id; | ||||
| 		// Slic3r::Point objects in scaled G-code coordinates
 | ||||
|     	Point 	shift; | ||||
|     	// Rotation along the Z axis, in radians.
 | ||||
|     	float 	rotation; | ||||
| 	}; | ||||
|         Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} | ||||
|         bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } | ||||
|         // ID of the corresponding ModelInstance.
 | ||||
|         ModelID instance_id; | ||||
|         // Slic3r::Point objects in scaled G-code coordinates
 | ||||
|         Point 	shift; | ||||
|         // Rotation along the Z axis, in radians.
 | ||||
|         float 	rotation; | ||||
|     }; | ||||
|     const std::vector<Instance>& instances() const { return m_instances; } | ||||
| 
 | ||||
|     bool                    has_mesh(SLAPrintObjectStep step) const; | ||||
|  | @ -142,15 +142,19 @@ public: | |||
|     }; | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     template <class T> inline static T level(const SliceRecord& sr) { | ||||
|     template<class T> inline static T level(const SliceRecord &sr) | ||||
|     { | ||||
|         static_assert(std::is_arithmetic<T>::value, "Arithmetic only!"); | ||||
|         return std::is_integral<T>::value ? T(sr.print_level()) : T(sr.slice_level()); | ||||
|         return std::is_integral<T>::value ? T(sr.print_level()) | ||||
|                                           : T(sr.slice_level()); | ||||
|     } | ||||
| 
 | ||||
|     template <class T> inline static SliceRecord create_slice_record(T val) { | ||||
|     template<class T> inline static SliceRecord create_slice_record(T val) | ||||
|     { | ||||
|         static_assert(std::is_arithmetic<T>::value, "Arithmetic only!"); | ||||
|         return std::is_integral<T>::value ? SliceRecord{ coord_t(val), 0.f, 0.f } : SliceRecord{ 0, float(val), 0.f }; | ||||
|         return std::is_integral<T>::value | ||||
|                    ? SliceRecord{coord_t(val), 0.f, 0.f} | ||||
|                    : SliceRecord{0, float(val), 0.f}; | ||||
|     } | ||||
| 
 | ||||
|     // This is a template method for searching the slice index either by
 | ||||
|  | @ -241,11 +245,11 @@ protected: | |||
|     ~SLAPrintObject(); | ||||
| 
 | ||||
|     void                    config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } | ||||
|     void                    config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false)  | ||||
|     	{ this->m_config.apply_only(other, keys, ignore_nonexistent); } | ||||
|     void                    config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) | ||||
|         { this->m_config.apply_only(other, keys, ignore_nonexistent); } | ||||
| 
 | ||||
|     void                    set_trafo(const Transform3d& trafo, bool left_handed) { | ||||
| 		m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); | ||||
|         m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); | ||||
|     } | ||||
| 
 | ||||
|     template<class InstVec> inline void set_instances(InstVec&& instances) { m_instances = std::forward<InstVec>(instances); } | ||||
|  | @ -322,37 +326,6 @@ struct SLAPrintStatistics | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| // The implementation of creating zipped archives with wxWidgets
 | ||||
| template<> class LayerWriter<Zipper> { | ||||
|     Zipper m_zip; | ||||
| public: | ||||
| 
 | ||||
|     LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {} | ||||
| 
 | ||||
|     void next_entry(const std::string& fname) { m_zip.add_entry(fname); } | ||||
| 
 | ||||
|     void binary_entry(const std::string& fname, | ||||
|                       const std::uint8_t* buf, | ||||
|                       size_t l) | ||||
|     { | ||||
|         m_zip.add_entry(fname, buf, l); | ||||
|     } | ||||
| 
 | ||||
|     template<class T> inline LayerWriter& operator<<(T&& arg) { | ||||
|         m_zip << std::forward<T>(arg); return *this; | ||||
|     } | ||||
| 
 | ||||
|     bool is_ok() const { | ||||
|         return true; // m_zip blows up if something goes wrong...
 | ||||
|     } | ||||
| 
 | ||||
|     // After finalize, no writing to the archive will have an effect. The only
 | ||||
|     // valid operation is to dispose the object calling the destructor which
 | ||||
|     // should close the file. This method can throw and signal potential errors
 | ||||
|     // when flushing the archive. This is why its present.
 | ||||
|     void finalize() { m_zip.finalize(); } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief This class is the high level FSM for the SLA printing process. | ||||
|  * | ||||
|  | @ -380,16 +353,15 @@ public: | |||
|     void                set_task(const TaskParams ¶ms) override; | ||||
|     void                process() override; | ||||
|     void                finalize() override; | ||||
|     // Returns true if an object step is done on all objects and there's at least one object.    
 | ||||
|     // Returns true if an object step is done on all objects and there's at least one object.
 | ||||
|     bool                is_step_done(SLAPrintObjectStep step) const; | ||||
|     // Returns true if the last step was finished with success.
 | ||||
|     bool                finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } | ||||
| 
 | ||||
|     template<class Fmt = Zipper> | ||||
|     inline void export_raster(const std::string& fpath, | ||||
|                        const std::string& projectname = "") | ||||
|                               const std::string& projectname = "") | ||||
|     { | ||||
|         if(m_printer) m_printer->save<Fmt>(fpath, projectname); | ||||
|         if(m_printer) m_printer->save(fpath, projectname); | ||||
|     } | ||||
| 
 | ||||
|     const PrintObjects& objects() const { return m_objects; } | ||||
|  | @ -450,7 +422,7 @@ public: | |||
|     const std::vector<PrintLayer>& print_layers() const { return m_printer_input; } | ||||
| 
 | ||||
| private: | ||||
|     using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>; | ||||
|     using SLAPrinter = sla::SLARasterWriter; | ||||
|     using SLAPrinterPtr = std::unique_ptr<SLAPrinter>; | ||||
| 
 | ||||
|     // Implement same logic as in SLAPrintObject
 | ||||
|  |  | |||
|  | @ -227,7 +227,7 @@ std::vector<coordf_t> layer_height_profile_adaptive( | |||
|     as.set_slicing_parameters(slicing_params); | ||||
|     for (const ModelVolume *volume : volumes) | ||||
|         if (volume->is_model_part()) | ||||
|             as.add_mesh(&volume->mesh); | ||||
|             as.add_mesh(&volume->mesh()); | ||||
|     as.prepare(); | ||||
| 
 | ||||
|     // 2) Generate layers using the algorithm of @platsch 
 | ||||
|  |  | |||
|  | @ -27,8 +27,8 @@ void SlicingAdaptive::prepare() | |||
| 		nfaces_total += (*it_mesh)->stl.stats.number_of_facets; | ||||
| 	m_faces.reserve(nfaces_total); | ||||
| 	for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) | ||||
| 		for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i) | ||||
| 			m_faces.push_back((*it_mesh)->stl.facet_start + i); | ||||
| 		for (const stl_facet &face : (*it_mesh)->stl.facet_start) | ||||
| 			m_faces.emplace_back(&face); | ||||
| 
 | ||||
| 	// 2) Sort faces lexicographically by their Z span.
 | ||||
| 	std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { | ||||
|  |  | |||
|  | @ -42,20 +42,17 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets) | ||||
|     : repaired(false) | ||||
| TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets) : repaired(false) | ||||
| { | ||||
|     stl_initialize(&this->stl); | ||||
|     stl_file &stl = this->stl; | ||||
|     stl.error = 0; | ||||
|     stl.stats.type = inmemory; | ||||
| 
 | ||||
|     // count facets and allocate memory
 | ||||
|     stl.stats.number_of_facets = facets.size(); | ||||
|     stl.stats.number_of_facets = (uint32_t)facets.size(); | ||||
|     stl.stats.original_num_facets = stl.stats.number_of_facets; | ||||
|     stl_allocate(&stl); | ||||
| 
 | ||||
|     for (uint32_t i = 0; i < stl.stats.number_of_facets; i++) { | ||||
| 	for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { | ||||
|         stl_facet facet; | ||||
|         facet.vertex[0] = points[facets[i](0)].cast<float>(); | ||||
|         facet.vertex[1] = points[facets[i](1)].cast<float>(); | ||||
|  | @ -73,57 +70,37 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f | |||
|     stl_get_size(&stl); | ||||
| } | ||||
| 
 | ||||
| TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) | ||||
| { | ||||
|     stl_close(&this->stl); | ||||
|     this->stl       = other.stl; | ||||
|     this->repaired  = other.repaired; | ||||
|     this->stl.heads = nullptr; | ||||
|     this->stl.tail  = nullptr; | ||||
|     this->stl.error = other.stl.error; | ||||
|     if (other.stl.facet_start != nullptr) { | ||||
|         this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet)); | ||||
|         std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start); | ||||
|     } | ||||
|     if (other.stl.neighbors_start != nullptr) { | ||||
|         this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors)); | ||||
|         std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start); | ||||
|     } | ||||
|     if (other.stl.v_indices != nullptr) { | ||||
|         this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct)); | ||||
|         std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices); | ||||
|     } | ||||
|     if (other.stl.v_shared != nullptr) { | ||||
|         this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex)); | ||||
|         std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared); | ||||
|     } | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| // #define SLIC3R_TRACE_REPAIR
 | ||||
| 
 | ||||
| void TriangleMesh::repair() | ||||
| void TriangleMesh::repair(bool update_shared_vertices) | ||||
| { | ||||
|     if (this->repaired) return; | ||||
|      | ||||
|     if (this->repaired) { | ||||
|     	if (update_shared_vertices) | ||||
|     		this->require_shared_vertices(); | ||||
|     	return; | ||||
|     } | ||||
| 
 | ||||
|     // admesh fails when repairing empty meshes
 | ||||
|     if (this->stl.stats.number_of_facets == 0) return; | ||||
|     if (this->stl.stats.number_of_facets == 0) | ||||
|     	return; | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; | ||||
|      | ||||
| 
 | ||||
|     // checking exact
 | ||||
| #ifdef SLIC3R_TRACE_REPAIR | ||||
| 	BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; | ||||
| #endif /* SLIC3R_TRACE_REPAIR */ | ||||
| 	assert(stl_validate(&this->stl)); | ||||
| 	stl_check_facets_exact(&stl); | ||||
|     assert(stl_validate(&this->stl)); | ||||
|     stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); | ||||
|     stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); | ||||
|     stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); | ||||
|      | ||||
|     // checking nearby
 | ||||
|     //int last_edges_fixed = 0;
 | ||||
| 	float tolerance = stl.stats.shortest_edge; | ||||
|     float increment = stl.stats.bounding_diameter / 10000.0; | ||||
| 	float tolerance = (float)stl.stats.shortest_edge; | ||||
| 	float increment = (float)stl.stats.bounding_diameter / 10000.0f; | ||||
|     int iterations = 2; | ||||
|     if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { | ||||
|         for (int i = 0; i < iterations; i++) { | ||||
|  | @ -141,6 +118,7 @@ void TriangleMesh::repair() | |||
|             } | ||||
|         } | ||||
|     } | ||||
|     assert(stl_validate(&this->stl)); | ||||
|      | ||||
|     // remove_unconnected
 | ||||
|     if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { | ||||
|  | @ -148,6 +126,7 @@ void TriangleMesh::repair() | |||
|         BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; | ||||
| #endif /* SLIC3R_TRACE_REPAIR */ | ||||
|         stl_remove_unconnected_facets(&stl); | ||||
| 	    assert(stl_validate(&this->stl)); | ||||
|     } | ||||
|      | ||||
|     // fill_holes
 | ||||
|  | @ -168,28 +147,38 @@ void TriangleMesh::repair() | |||
|     BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; | ||||
| #endif /* SLIC3R_TRACE_REPAIR */ | ||||
|     stl_fix_normal_directions(&stl); | ||||
|     assert(stl_validate(&this->stl)); | ||||
| 
 | ||||
|     // normal_values
 | ||||
| #ifdef SLIC3R_TRACE_REPAIR | ||||
|     BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; | ||||
| #endif /* SLIC3R_TRACE_REPAIR */ | ||||
|     stl_fix_normal_values(&stl); | ||||
|     assert(stl_validate(&this->stl)); | ||||
|      | ||||
|     // always calculate the volume and reverse all normals if volume is negative
 | ||||
| #ifdef SLIC3R_TRACE_REPAIR | ||||
|     BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; | ||||
| #endif /* SLIC3R_TRACE_REPAIR */ | ||||
|     stl_calculate_volume(&stl); | ||||
|     assert(stl_validate(&this->stl)); | ||||
|      | ||||
|     // neighbors
 | ||||
| #ifdef SLIC3R_TRACE_REPAIR | ||||
|     BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; | ||||
| #endif /* SLIC3R_TRACE_REPAIR */ | ||||
|     stl_verify_neighbors(&stl); | ||||
|     assert(stl_validate(&this->stl)); | ||||
| 
 | ||||
|     this->repaired = true; | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; | ||||
| 
 | ||||
|     // This call should be quite cheap, a lot of code requires the indexed_triangle_set data structure,
 | ||||
|     // and it is risky to generate such a structure once the meshes are shared. Do it now.
 | ||||
|     this->its.clear(); | ||||
|     if (update_shared_vertices) | ||||
|     	this->require_shared_vertices(); | ||||
| } | ||||
| 
 | ||||
| float TriangleMesh::volume() | ||||
|  | @ -249,20 +238,24 @@ bool TriangleMesh::needed_repair() const | |||
| 
 | ||||
| void TriangleMesh::WriteOBJFile(const char* output_file) | ||||
| { | ||||
|     stl_generate_shared_vertices(&stl); | ||||
|     stl_write_obj(&stl, output_file); | ||||
|     its_write_obj(this->its, output_file); | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::scale(float factor) | ||||
| { | ||||
|     stl_scale(&(this->stl), factor); | ||||
|     stl_invalidate_shared_vertices(&this->stl); | ||||
| 	for (stl_vertex& v : this->its.vertices) | ||||
| 		v *= factor; | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::scale(const Vec3d &versor) | ||||
| { | ||||
|     stl_scale_versor(&this->stl, versor.cast<float>()); | ||||
|     stl_invalidate_shared_vertices(&this->stl); | ||||
| 	for (stl_vertex& v : this->its.vertices) { | ||||
| 		v.x() *= versor.x(); | ||||
| 		v.y() *= versor.y(); | ||||
| 		v.z() *= versor.z(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::translate(float x, float y, float z) | ||||
|  | @ -270,7 +263,9 @@ void TriangleMesh::translate(float x, float y, float z) | |||
|     if (x == 0.f && y == 0.f && z == 0.f) | ||||
|         return; | ||||
|     stl_translate_relative(&(this->stl), x, y, z); | ||||
|     stl_invalidate_shared_vertices(&this->stl); | ||||
| 	stl_vertex shift(x, y, z); | ||||
| 	for (stl_vertex& v : this->its.vertices) | ||||
| 		v += shift; | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::translate(const Vec3f &displacement) | ||||
|  | @ -287,13 +282,15 @@ void TriangleMesh::rotate(float angle, const Axis &axis) | |||
|     angle = Slic3r::Geometry::rad2deg(angle); | ||||
|      | ||||
|     if (axis == X) { | ||||
|         stl_rotate_x(&(this->stl), angle); | ||||
|         stl_rotate_x(&this->stl, angle); | ||||
|         its_rotate_x(this->its, angle); | ||||
|     } else if (axis == Y) { | ||||
|         stl_rotate_y(&(this->stl), angle); | ||||
|         stl_rotate_y(&this->stl, angle); | ||||
|         its_rotate_y(this->its, angle); | ||||
|     } else if (axis == Z) { | ||||
|         stl_rotate_z(&(this->stl), angle); | ||||
|         stl_rotate_z(&this->stl, angle); | ||||
|         its_rotate_z(this->its, angle); | ||||
|     } | ||||
|     stl_invalidate_shared_vertices(&this->stl); | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::rotate(float angle, const Vec3d& axis) | ||||
|  | @ -305,39 +302,49 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis) | |||
|     Transform3d m = Transform3d::Identity(); | ||||
|     m.rotate(Eigen::AngleAxisd(angle, axis_norm)); | ||||
|     stl_transform(&stl, m); | ||||
|     its_transform(its, m); | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::mirror(const Axis &axis) | ||||
| { | ||||
|     if (axis == X) { | ||||
|         stl_mirror_yz(&this->stl); | ||||
|         for (stl_vertex &v : this->its.vertices) | ||||
|       		v(0) *= -1.0; | ||||
|     } else if (axis == Y) { | ||||
|         stl_mirror_xz(&this->stl); | ||||
|         for (stl_vertex &v : this->its.vertices) | ||||
|       		v(1) *= -1.0; | ||||
|     } else if (axis == Z) { | ||||
|         stl_mirror_xy(&this->stl); | ||||
|         for (stl_vertex &v : this->its.vertices) | ||||
|       		v(2) *= -1.0; | ||||
|     } | ||||
|     stl_invalidate_shared_vertices(&this->stl); | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) | ||||
| { | ||||
|     stl_transform(&stl, t); | ||||
|     stl_invalidate_shared_vertices(&stl); | ||||
|     its_transform(its, t); | ||||
| 	if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { | ||||
| 		// Left handed transformation is being applied. It is a good idea to flip the faces and their normals.
 | ||||
| 		this->repair(); | ||||
| 		this->repair(false); | ||||
| 		stl_reverse_all_facets(&stl); | ||||
| 		this->its.clear(); | ||||
| 		this->require_shared_vertices(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) | ||||
| { | ||||
|     stl_transform(&stl, m); | ||||
|     stl_invalidate_shared_vertices(&stl); | ||||
|     its_transform(its, m); | ||||
|     if (fix_left_handed && m.determinant() < 0.) { | ||||
|         // Left handed transformation is being applied. It is a good idea to flip the faces and their normals.
 | ||||
|         this->repair(); | ||||
|         this->repair(false); | ||||
|         stl_reverse_all_facets(&stl); | ||||
| 		this->its.clear(); | ||||
| 		this->require_shared_vertices(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -355,7 +362,8 @@ void TriangleMesh::rotate(double angle, Point* center) | |||
|         return; | ||||
|     Vec2f c = center->cast<float>(); | ||||
|     this->translate(-c(0), -c(1), 0); | ||||
|     stl_rotate_z(&(this->stl), (float)angle); | ||||
|     stl_rotate_z(&this->stl, (float)angle); | ||||
|     its_rotate_z(this->its, (float)angle); | ||||
|     this->translate(c(0), c(1), 0); | ||||
| } | ||||
| 
 | ||||
|  | @ -435,9 +443,8 @@ TriangleMeshPtrs TriangleMesh::split() const | |||
|         TriangleMesh* mesh = new TriangleMesh; | ||||
|         meshes.emplace_back(mesh); | ||||
|         mesh->stl.stats.type = inmemory; | ||||
|         mesh->stl.stats.number_of_facets = facets.size(); | ||||
|         mesh->stl.stats.number_of_facets = (uint32_t)facets.size(); | ||||
|         mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; | ||||
|         stl_clear_error(&mesh->stl); | ||||
|         stl_allocate(&mesh->stl); | ||||
| 
 | ||||
|         // Assign the facets to the new mesh.
 | ||||
|  | @ -455,7 +462,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh) | |||
| { | ||||
|     // reset stats and metadata
 | ||||
|     int number_of_facets = this->stl.stats.number_of_facets; | ||||
|     stl_invalidate_shared_vertices(&this->stl); | ||||
|     this->its.clear(); | ||||
|     this->repaired = false; | ||||
|      | ||||
|     // update facet count and allocate more memory
 | ||||
|  | @ -477,13 +484,12 @@ ExPolygons TriangleMesh::horizontal_projection() const | |||
| { | ||||
|     Polygons pp; | ||||
|     pp.reserve(this->stl.stats.number_of_facets); | ||||
|     for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) { | ||||
|         stl_facet* facet = &this->stl.facet_start[i]; | ||||
| 	for (const stl_facet &facet : this->stl.facet_start) { | ||||
|         Polygon p; | ||||
|         p.points.resize(3); | ||||
|         p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1)); | ||||
|         p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1)); | ||||
|         p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1)); | ||||
|         p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); | ||||
|         p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); | ||||
|         p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); | ||||
|         p.make_counter_clockwise();  // do this after scaling, as winding order might change while doing that
 | ||||
|         pp.emplace_back(p); | ||||
|     } | ||||
|  | @ -495,11 +501,10 @@ ExPolygons TriangleMesh::horizontal_projection() const | |||
| // 2D convex hull of a 3D mesh projected into the Z=0 plane.
 | ||||
| Polygon TriangleMesh::convex_hull() | ||||
| { | ||||
|     this->require_shared_vertices(); | ||||
|     Points pp; | ||||
|     pp.reserve(this->stl.stats.shared_vertices); | ||||
|     for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) { | ||||
|         const stl_vertex &v = this->stl.v_shared[i]; | ||||
|     pp.reserve(this->its.vertices.size()); | ||||
|     for (size_t i = 0; i < this->its.vertices.size(); ++ i) { | ||||
|         const stl_vertex &v = this->its.vertices[i]; | ||||
|         pp.emplace_back(Point::new_scale(v(0), v(1))); | ||||
|     } | ||||
|     return Slic3r::Geometry::convex_hull(pp); | ||||
|  | @ -517,49 +522,47 @@ BoundingBoxf3 TriangleMesh::bounding_box() const | |||
| BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const | ||||
| { | ||||
|     BoundingBoxf3 bbox; | ||||
|     if (stl.v_shared == nullptr) { | ||||
|     if (this->its.vertices.empty()) { | ||||
|         // Using the STL faces.
 | ||||
|         for (size_t i = 0; i < this->facets_count(); ++ i) { | ||||
|             const stl_facet &facet = this->stl.facet_start[i]; | ||||
| 		for (const stl_facet &facet : this->stl.facet_start) | ||||
|             for (size_t j = 0; j < 3; ++ j) | ||||
|                 bbox.merge(trafo * facet.vertex[j].cast<double>()); | ||||
|         } | ||||
|     } else { | ||||
|         // Using the shared vertices should be a bit quicker than using the STL faces.
 | ||||
|         for (int i = 0; i < stl.stats.shared_vertices; ++ i)             | ||||
|             bbox.merge(trafo * this->stl.v_shared[i].cast<double>()); | ||||
| 		for (const stl_vertex &v : this->its.vertices) | ||||
|             bbox.merge(trafo * v.cast<double>()); | ||||
|     } | ||||
|     return bbox; | ||||
| } | ||||
| 
 | ||||
| TriangleMesh TriangleMesh::convex_hull_3d() const | ||||
| { | ||||
|     // Helper struct for qhull:
 | ||||
|     struct PointForQHull{ | ||||
|         PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {} | ||||
|         realT x, y, z; | ||||
|     }; | ||||
|     std::vector<PointForQHull> src_vertices; | ||||
| 
 | ||||
|     // We will now fill the vector with input points for computation:
 | ||||
|     stl_facet* facet_ptr = stl.facet_start; | ||||
|     while (facet_ptr < stl.facet_start + stl.stats.number_of_facets) | ||||
|     { | ||||
|         for (int i = 0; i < 3; ++i) | ||||
|         { | ||||
|             const stl_vertex& v = facet_ptr->vertex[i]; | ||||
|             src_vertices.emplace_back(v(0), v(1), v(2)); | ||||
|         } | ||||
| 
 | ||||
|         facet_ptr += 1; | ||||
|     } | ||||
| 
 | ||||
|     // The qhull call:
 | ||||
|     orgQhull::Qhull qhull; | ||||
|     qhull.disableOutputStream(); // we want qhull to be quiet
 | ||||
|     try | ||||
| 	std::vector<realT> src_vertices; | ||||
| 	try | ||||
|     { | ||||
|         qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt"); | ||||
|     	if (this->has_shared_vertices()) { | ||||
| #if REALfloat | ||||
| 	    	qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); | ||||
| #else | ||||
| 	    	src_vertices.reserve(this->its.vertices() * 3); | ||||
| 	    	// We will now fill the vector with input points for computation:
 | ||||
| 			for (const stl_vertex &v : ths->its.vertices.size()) | ||||
| 				for (int i = 0; i < 3; ++ i) | ||||
| 		        	src_vertices.emplace_back(v(i)); | ||||
| 	        qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); | ||||
| #endif | ||||
| 	    } else { | ||||
| 	    	src_vertices.reserve(this->stl.facet_start.size() * 9); | ||||
| 	    	// We will now fill the vector with input points for computation:
 | ||||
| 			for (const stl_facet &f : this->stl.facet_start) | ||||
| 				for (int i = 0; i < 3; ++ i) | ||||
| 					for (int j = 0; j < 3; ++ j) | ||||
| 		        		src_vertices.emplace_back(f.vertex[i](j)); | ||||
| 	        qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); | ||||
| 	    } | ||||
|     } | ||||
|     catch (...) | ||||
|     { | ||||
|  | @ -587,34 +590,20 @@ TriangleMesh TriangleMesh::convex_hull_3d() const | |||
| 
 | ||||
|     TriangleMesh output_mesh(dst_vertices, facets); | ||||
|     output_mesh.repair(); | ||||
|     output_mesh.require_shared_vertices(); | ||||
|     return output_mesh; | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::require_shared_vertices() | ||||
| { | ||||
|     BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; | ||||
|     if (!this->repaired)  | ||||
|     assert(stl_validate(&this->stl)); | ||||
|     if (! this->repaired)  | ||||
|         this->repair(); | ||||
|     if (this->stl.v_shared == NULL) { | ||||
|     if (this->its.vertices.empty()) { | ||||
|         BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; | ||||
|         stl_generate_shared_vertices(&(this->stl)); | ||||
|         stl_generate_shared_vertices(&this->stl, this->its); | ||||
|     } | ||||
| #ifdef _DEBUG | ||||
|     // Verify validity of neighborship data.
 | ||||
|     for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { | ||||
|         const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; | ||||
|         const int *vertices = stl.v_indices[facet_idx].vertex; | ||||
|         for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { | ||||
|             int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; | ||||
|             if (nbr_face != -1) { | ||||
| 				assert( | ||||
| 					(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]) || | ||||
| 					(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[nbr_idx])); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| #endif /* _DEBUG */ | ||||
|     assert(stl_validate(&this->stl, this->its)); | ||||
|     BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; | ||||
| } | ||||
| 
 | ||||
|  | @ -626,10 +615,9 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac | |||
| 
 | ||||
|     throw_on_cancel(); | ||||
|     facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); | ||||
|     v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices); | ||||
|     // Scale the copied vertices.
 | ||||
|     for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) | ||||
|         this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR); | ||||
| 	v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex()); | ||||
| 	for (size_t i = 0; i < v_scaled_shared.size(); ++ i) | ||||
|         this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR); | ||||
| 
 | ||||
|     // Create a mapping from triangle edge into face.
 | ||||
|     struct EdgeToFace { | ||||
|  | @ -649,8 +637,8 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac | |||
|     for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) | ||||
|         for (int i = 0; i < 3; ++ i) { | ||||
|             EdgeToFace &e2f = edges_map[facet_idx*3+i]; | ||||
|             e2f.vertex_low  = this->mesh->stl.v_indices[facet_idx].vertex[i]; | ||||
|             e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3]; | ||||
|             e2f.vertex_low  = this->mesh->its.indices[facet_idx][i]; | ||||
|             e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3]; | ||||
|             e2f.face        = facet_idx; | ||||
|             // 1 based indexing, to be always strictly positive.
 | ||||
|             e2f.face_edge   = i + 1; | ||||
|  | @ -818,7 +806,7 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons | |||
| void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,  | ||||
|     const std::vector<float> &z) const | ||||
| { | ||||
|     const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx]; | ||||
|     const stl_facet &facet = m_use_quaternion ? (this->mesh->stl.facet_start.data() + facet_idx)->rotated(m_quaternion) : *(this->mesh->stl.facet_start.data() + facet_idx); | ||||
|      | ||||
|     // find facet extents
 | ||||
|     const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); | ||||
|  | @ -887,7 +875,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( | |||
|     // Reorder vertices so that the first one is the one with lowest Z.
 | ||||
|     // This is needed to get all intersection lines in a consistent order
 | ||||
|     // (external on the right of the line)
 | ||||
|     const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; | ||||
|     const stl_triangle_vertex_indices &vertices = this->mesh->its.indices[facet_idx]; | ||||
|     int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); | ||||
| 
 | ||||
|     // These are used only if the cut plane is tilted:
 | ||||
|  | @ -1714,7 +1702,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) | |||
|     BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; | ||||
|     float scaled_z = scale_(z); | ||||
|     for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { | ||||
|         stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; | ||||
|         const stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; | ||||
|          | ||||
|         // find facet extents
 | ||||
|         float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); | ||||
|  | @ -1736,10 +1724,12 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) | |||
|          | ||||
|         if (min_z > z || (min_z == z && max_z > z)) { | ||||
|             // facet is above the cut plane and does not belong to it
 | ||||
|             if (upper != NULL) stl_add_facet(&upper->stl, facet); | ||||
|             if (upper != nullptr) | ||||
| 				stl_add_facet(&upper->stl, facet); | ||||
|         } else if (max_z < z || (max_z == z && min_z < z)) { | ||||
|             // facet is below the cut plane and does not belong to it
 | ||||
|             if (lower != NULL) stl_add_facet(&lower->stl, facet); | ||||
|             if (lower != nullptr) | ||||
| 				stl_add_facet(&lower->stl, facet); | ||||
|         } else if (min_z < z && max_z > z) { | ||||
|             // Facet is cut by the slicing plane.
 | ||||
| 
 | ||||
|  | @ -1786,22 +1776,24 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) | |||
|             quadrilateral[1].vertex[2] = v0v1; | ||||
|              | ||||
|             if (v0(2) > z) { | ||||
|                 if (upper != NULL) stl_add_facet(&upper->stl, &triangle); | ||||
|                 if (lower != NULL) { | ||||
|                 if (upper != nullptr)  | ||||
| 					stl_add_facet(&upper->stl, &triangle); | ||||
|                 if (lower != nullptr) { | ||||
|                     stl_add_facet(&lower->stl, &quadrilateral[0]); | ||||
|                     stl_add_facet(&lower->stl, &quadrilateral[1]); | ||||
|                 } | ||||
|             } else { | ||||
|                 if (upper != NULL) { | ||||
|                 if (upper != nullptr) { | ||||
|                     stl_add_facet(&upper->stl, &quadrilateral[0]); | ||||
|                     stl_add_facet(&upper->stl, &quadrilateral[1]); | ||||
|                 } | ||||
|                 if (lower != NULL) stl_add_facet(&lower->stl, &triangle); | ||||
|                 if (lower != nullptr)  | ||||
| 					stl_add_facet(&lower->stl, &triangle); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     if (upper != NULL) { | ||||
|     if (upper != nullptr) { | ||||
|         BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; | ||||
|         ExPolygons section; | ||||
|         this->make_expolygons_simple(upper_lines, §ion); | ||||
|  | @ -1815,7 +1807,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) | |||
|         } | ||||
|     } | ||||
|      | ||||
|     if (lower != NULL) { | ||||
|     if (lower != nullptr) { | ||||
|         BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; | ||||
|         ExPolygons section; | ||||
|         this->make_expolygons_simple(lower_lines, §ion); | ||||
|  | @ -1905,10 +1897,10 @@ TriangleMesh make_cylinder(double r, double h, double fa) | |||
| //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html
 | ||||
| TriangleMesh make_sphere(double radius, double fa) | ||||
| { | ||||
| 	int   sectorCount = ceil(2. * M_PI / fa); | ||||
| 	int   stackCount  = ceil(M_PI / fa); | ||||
| 	float sectorStep  = 2. * M_PI / sectorCount; | ||||
| 	float stackStep   = M_PI / stackCount; | ||||
| 	int   sectorCount = int(ceil(2. * M_PI / fa)); | ||||
| 	int   stackCount  = int(ceil(M_PI / fa)); | ||||
| 	float sectorStep  = float(2. * M_PI / sectorCount); | ||||
| 	float stackStep   = float(M_PI / stackCount); | ||||
| 
 | ||||
| 	Pointf3s vertices; | ||||
| 	vertices.reserve((stackCount - 1) * sectorCount + 2); | ||||
|  |  | |||
|  | @ -21,19 +21,13 @@ typedef std::vector<TriangleMesh*> TriangleMeshPtrs; | |||
| class TriangleMesh | ||||
| { | ||||
| public: | ||||
|     TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } | ||||
|     TriangleMesh() : repaired(false) {} | ||||
|     TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets); | ||||
|     TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } | ||||
|     TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } | ||||
|     ~TriangleMesh() { clear(); } | ||||
|     TriangleMesh& operator=(const TriangleMesh &other); | ||||
|     TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } | ||||
|     void clear() { stl_close(&this->stl); this->repaired = false; } | ||||
|     void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } | ||||
|     void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } | ||||
|     void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } | ||||
|     void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); } | ||||
|     void repair(); | ||||
| 	void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } | ||||
|     bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } | ||||
|     bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } | ||||
|     bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } | ||||
|     void repair(bool update_shared_vertices = true); | ||||
|     float volume(); | ||||
|     void check_topology(); | ||||
|     bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } | ||||
|  | @ -58,7 +52,7 @@ public: | |||
|     TriangleMeshPtrs split() const; | ||||
|     void merge(const TriangleMesh &mesh); | ||||
|     ExPolygons horizontal_projection() const; | ||||
|     const float* first_vertex() const { return this->stl.facet_start ? &this->stl.facet_start->vertex[0](0) : nullptr; } | ||||
|     const float* first_vertex() const { return this->stl.facet_start.empty() ? nullptr : &this->stl.facet_start.front().vertex[0](0); } | ||||
|     // 2D convex hull of a 3D mesh projected into the Z=0 plane.
 | ||||
|     Polygon convex_hull(); | ||||
|     BoundingBoxf3 bounding_box() const; | ||||
|  | @ -69,12 +63,13 @@ public: | |||
|     void reset_repair_stats(); | ||||
|     bool needed_repair() const; | ||||
|     void require_shared_vertices(); | ||||
|     bool   has_shared_vertices() const { return stl.v_shared != NULL; } | ||||
|     bool   has_shared_vertices() const { return ! this->its.vertices.empty(); } | ||||
|     size_t facets_count() const { return this->stl.stats.number_of_facets; } | ||||
|     bool   empty() const { return this->facets_count() == 0; } | ||||
|     bool is_splittable() const; | ||||
| 
 | ||||
|     stl_file stl; | ||||
|     indexed_triangle_set its; | ||||
|     bool repaired; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -48,10 +48,33 @@ typedef double  coordf_t; | |||
| //FIXME Better to use an inline function with an explicit return type.
 | ||||
| //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
 | ||||
| #define scale_(val) ((val) / SCALING_FACTOR) | ||||
| 
 | ||||
| #define SCALED_EPSILON scale_(EPSILON) | ||||
| 
 | ||||
| #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" | ||||
| 
 | ||||
| #if defined(_MSC_VER) &&  _MSC_VER < 1900 | ||||
| # define SLIC3R_CONSTEXPR | ||||
| # define SLIC3R_NOEXCEPT | ||||
| #else | ||||
| #define SLIC3R_CONSTEXPR constexpr | ||||
| #define SLIC3R_NOEXCEPT  noexcept | ||||
| #endif | ||||
| 
 | ||||
| template<class Tf> inline SLIC3R_CONSTEXPR coord_t scaled(Tf val) | ||||
| { | ||||
|     static_assert (std::is_floating_point<Tf>::value, "Floating point only"); | ||||
|     return coord_t(val / Tf(SCALING_FACTOR)); | ||||
| } | ||||
| 
 | ||||
| template<class Tf = double> inline SLIC3R_CONSTEXPR Tf unscaled(coord_t val) | ||||
| { | ||||
|     static_assert (std::is_floating_point<Tf>::value, "Floating point only"); | ||||
|     return Tf(val * Tf(SCALING_FACTOR)); | ||||
| } | ||||
| 
 | ||||
| inline SLIC3R_CONSTEXPR float unscaledf(coord_t val) { return unscaled<float>(val); } | ||||
| 
 | ||||
| inline std::string debug_out_path(const char *name, ...) | ||||
| { | ||||
| 	char buffer[2048]; | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ add_library(qhull INTERFACE) | |||
| if(Qhull_FOUND) | ||||
| 
 | ||||
| message(STATUS "Using qhull from system.") | ||||
| if(SLICER_STATIC) | ||||
| if(SLIC3R_STATIC) | ||||
|     target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r) | ||||
| else() | ||||
|     target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r) | ||||
|  |  | |||
|  | @ -198,6 +198,11 @@ size_t Index::load(const boost::filesystem::path &path) | |||
|     size_t idx_line = 0; | ||||
|     Version ver; | ||||
|     while (std::getline(ifs, line)) { | ||||
| #ifndef _MSVCVER | ||||
| 		// On a Unix system, getline does not remove the trailing carriage returns, if the index is shared over a Windows filesystem. Remove them manually.
 | ||||
| 		while (! line.empty() && line.back() == '\r') | ||||
| 			line.pop_back(); | ||||
| #endif | ||||
|     	++ idx_line; | ||||
|     	// Skip the initial white spaces.
 | ||||
|     	char *key = left_trim(const_cast<char*>(line.data())); | ||||
|  |  | |||
|  | @ -19,8 +19,6 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), - | |||
|     m_user_drawn_background = false; | ||||
| #endif /*__APPLE__*/ | ||||
|     Bind(wxEVT_PAINT, ([this](wxPaintEvent &/* e */) { repaint(); })); | ||||
|     Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent &event) { mouse_event(event); })); | ||||
|     Bind(wxEVT_MOTION, ([this](wxMouseEvent &event) { mouse_event(event); })); | ||||
|     Bind(wxEVT_SIZE, ([this](wxSizeEvent & /* e */) { Refresh(); })); | ||||
| } | ||||
| void Bed_2D::repaint() | ||||
|  | @ -43,22 +41,14 @@ void Bed_2D::repaint() | |||
| 		dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); | ||||
| 	} | ||||
| 
 | ||||
| 	// turn cw and ch from sizes to max coordinates
 | ||||
| 	cw--; | ||||
| 	ch--; | ||||
|     if (m_bed_shape.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     // reduce size to have some space around the drawn shape
 | ||||
|     cw -= (2 * Border); | ||||
|     ch -= (2 * Border); | ||||
| 
 | ||||
| 	auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch)); | ||||
| 	// leave space for origin point
 | ||||
| 	cbb.min(0) += 4; | ||||
| 	cbb.max -= Vec2d(4., 4.); | ||||
| 
 | ||||
| 	// leave space for origin label
 | ||||
| 	cbb.max(1) -= 13; | ||||
| 
 | ||||
| 	// read new size
 | ||||
| 	cw = cbb.size()(0); | ||||
| 	ch = cbb.size()(1); | ||||
| 
 | ||||
| 	auto ccenter = cbb.center(); | ||||
| 
 | ||||
| 	// get bounding box of bed shape in G - code coordinates
 | ||||
|  | @ -76,17 +66,17 @@ void Bed_2D::repaint() | |||
| 		ccenter(0) - bcenter(0) * sfactor, | ||||
| 		ccenter(1) - bcenter(1) * sfactor | ||||
| 		); | ||||
| 
 | ||||
| 	m_scale_factor = sfactor; | ||||
| 	m_shift = Vec2d(shift(0) + cbb.min(0), | ||||
| 					shift(1) - (cbb.max(1) - GetSize().GetHeight())); | ||||
|     m_shift = Vec2d(shift(0) + cbb.min(0), shift(1) - (cbb.max(1) - ch)); | ||||
| 
 | ||||
| 	// draw bed fill
 | ||||
| 	dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); | ||||
| 	wxPointList pt_list; | ||||
| 	for (auto pt: m_bed_shape) | ||||
| 	{ | ||||
| 		Point pt_pix = to_pixels(pt); | ||||
| 		pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); | ||||
|         Point pt_pix = to_pixels(pt, ch); | ||||
|         pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); | ||||
| 	} | ||||
| 	dc.DrawPolygon(&pt_list, 0, 0); | ||||
| 
 | ||||
|  | @ -105,9 +95,9 @@ void Bed_2D::repaint() | |||
| 	for (auto pl : polylines) | ||||
| 	{ | ||||
| 		for (size_t i = 0; i < pl.points.size()-1; i++) { | ||||
| 			Point pt1 = to_pixels(unscale(pl.points[i])); | ||||
| 			Point pt2 = to_pixels(unscale(pl.points[i+1])); | ||||
| 			dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); | ||||
|             Point pt1 = to_pixels(unscale(pl.points[i]), ch); | ||||
|             Point pt2 = to_pixels(unscale(pl.points[i + 1]), ch); | ||||
|             dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -116,7 +106,7 @@ void Bed_2D::repaint() | |||
| 	dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); | ||||
| 	dc.DrawPolygon(&pt_list, 0, 0); | ||||
| 
 | ||||
| 	auto origin_px = to_pixels(Vec2d(0, 0)); | ||||
|     auto origin_px = to_pixels(Vec2d(0, 0), ch); | ||||
| 
 | ||||
| 	// draw axes
 | ||||
| 	auto axes_len = 50; | ||||
|  | @ -153,7 +143,7 @@ void Bed_2D::repaint() | |||
| 
 | ||||
| 	// draw current position
 | ||||
| 	if (m_pos!= Vec2d(0, 0)) { | ||||
| 		auto pos_px = to_pixels(m_pos); | ||||
|         auto pos_px = to_pixels(m_pos, ch); | ||||
|         dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxPENSTYLE_SOLID)); | ||||
|         dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); | ||||
| 		dc.DrawCircle(pos_px(0), pos_px(1), 5); | ||||
|  | @ -161,35 +151,14 @@ void Bed_2D::repaint() | |||
| 		dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1)); | ||||
| 		dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15); | ||||
| 	} | ||||
| 
 | ||||
| 	m_painted = true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // convert G - code coordinates into pixels
 | ||||
| Point Bed_2D::to_pixels(Vec2d point) | ||||
| Point Bed_2D::to_pixels(Vec2d point, int height) | ||||
| { | ||||
| 	auto p = point * m_scale_factor + m_shift; | ||||
| 	return Point(p(0), GetSize().GetHeight() - p(1));  | ||||
| } | ||||
| 
 | ||||
| void Bed_2D::mouse_event(wxMouseEvent event) | ||||
| { | ||||
| 	if (!m_interactive) return; | ||||
| 	if (!m_painted) return; | ||||
| 
 | ||||
| 	auto pos = event.GetPosition(); | ||||
| 	auto point = to_units(Point(pos.x, pos.y)); | ||||
| 	if (event.LeftDown() || event.Dragging()) { | ||||
| 		if (m_on_move) | ||||
| 			m_on_move(point) ; | ||||
| 		Refresh(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // convert pixels into G - code coordinates
 | ||||
| Vec2d Bed_2D::to_units(Point point) | ||||
| { | ||||
| 	return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor); | ||||
|     return Point(p(0) + Border, height - p(1) + Border); | ||||
| } | ||||
| 
 | ||||
| void Bed_2D::set_pos(Vec2d pos) | ||||
|  |  | |||
|  | @ -9,20 +9,17 @@ namespace GUI { | |||
| 
 | ||||
| class Bed_2D : public wxPanel | ||||
| { | ||||
|     static const int Border = 10; | ||||
| 
 | ||||
| 	bool		m_user_drawn_background = true; | ||||
| 
 | ||||
| 	bool		m_painted = false; | ||||
| 	bool		m_interactive = false; | ||||
| 	double		m_scale_factor; | ||||
|     double		m_scale_factor; | ||||
| 	Vec2d		m_shift = Vec2d::Zero(); | ||||
| 	Vec2d		m_pos = Vec2d::Zero(); | ||||
| 	std::function<void(Vec2d)>	m_on_move = nullptr; | ||||
| 
 | ||||
| 	Point		to_pixels(Vec2d point); | ||||
| 	Vec2d		to_units(Point point); | ||||
| 	void		repaint(); | ||||
| 	void		mouse_event(wxMouseEvent event); | ||||
| 	void		set_pos(Vec2d pos); | ||||
|     Point		to_pixels(Vec2d point, int height); | ||||
|     void		repaint(); | ||||
|     void		set_pos(Vec2d pos); | ||||
| 
 | ||||
| public: | ||||
|     Bed_2D(wxWindow* parent); | ||||
|  |  | |||
|  | @ -241,8 +241,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) | |||
|     : m_transformed_bounding_box_dirty(true) | ||||
|     , m_sla_shift_z(0.0) | ||||
|     , m_transformed_convex_hull_bounding_box_dirty(true) | ||||
|     , m_convex_hull(nullptr) | ||||
|     , m_convex_hull_owned(false) | ||||
|     // geometry_id == 0 -> invalid
 | ||||
|     , geometry_id(std::pair<size_t, size_t>(0, 0)) | ||||
|     , extruder_id(0) | ||||
|  | @ -268,12 +266,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) | |||
|     set_render_color(r, g, b, a); | ||||
| } | ||||
| 
 | ||||
| GLVolume::~GLVolume() | ||||
| { | ||||
|     if (m_convex_hull_owned) | ||||
|         delete m_convex_hull; | ||||
| } | ||||
| 
 | ||||
| void GLVolume::set_render_color(float r, float g, float b, float a) | ||||
| { | ||||
|     render_color[0] = r; | ||||
|  | @ -335,12 +327,6 @@ void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume) | |||
|     color[3] = model_volume->is_model_part() ? 1.f : 0.5f; | ||||
| } | ||||
| 
 | ||||
| void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned) | ||||
| { | ||||
|     m_convex_hull = convex_hull; | ||||
|     m_convex_hull_owned = owned; | ||||
| } | ||||
| 
 | ||||
| Transform3d GLVolume::world_matrix() const | ||||
| { | ||||
|     Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); | ||||
|  | @ -377,7 +363,7 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const | |||
| 
 | ||||
| BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const | ||||
| { | ||||
| 	return (m_convex_hull != nullptr && m_convex_hull->stl.stats.number_of_facets > 0) ?  | ||||
| 	return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ?  | ||||
| 		m_convex_hull->transformed_bounding_box(trafo) : | ||||
| 		bounding_box.transformed(trafo); | ||||
| } | ||||
|  | @ -587,7 +573,7 @@ int GLVolumeCollection::load_object_volume( | |||
|     const ModelVolume   *model_volume = model_object->volumes[volume_idx]; | ||||
|     const int            extruder_id  = model_volume->extruder_id(); | ||||
|     const ModelInstance *instance     = model_object->instances[instance_idx]; | ||||
|     const TriangleMesh& mesh = model_volume->mesh; | ||||
|     const TriangleMesh& mesh = model_volume->mesh(); | ||||
|     float color[4]; | ||||
|     memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); | ||||
| /*    if (model_volume->is_support_blocker()) {
 | ||||
|  | @ -613,7 +599,7 @@ int GLVolumeCollection::load_object_volume( | |||
|     if (model_volume->is_model_part()) | ||||
|     { | ||||
| 		// GLVolume will reference a convex hull from model_volume!
 | ||||
|         v.set_convex_hull(&model_volume->get_convex_hull(), false); | ||||
|         v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); | ||||
|         if (extruder_id != -1) | ||||
|             v.extruder_id = extruder_id; | ||||
|     } | ||||
|  | @ -656,7 +642,10 @@ void GLVolumeCollection::load_object_auxiliary( | |||
|         v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); | ||||
|         v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id); | ||||
| 		// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
 | ||||
| 		v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); | ||||
|         if (&instance_idx == &instances.back()) | ||||
|             v.set_convex_hull(std::move(convex_hull)); | ||||
|         else | ||||
|             v.set_convex_hull(convex_hull); | ||||
|         v.is_modifier  = false; | ||||
|         v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); | ||||
|         v.set_instance_transformation(model_instance.get_transformation()); | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include "slic3r/GUI/GLCanvas3DManager.hpp" | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| #define HAS_GLSAFE | ||||
|  | @ -243,7 +244,6 @@ public: | |||
| 
 | ||||
|     GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); | ||||
|     GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} | ||||
|     ~GLVolume(); | ||||
| 
 | ||||
| private: | ||||
|     Geometry::Transformation m_instance_transformation; | ||||
|  | @ -255,10 +255,8 @@ private: | |||
|     mutable BoundingBoxf3 m_transformed_bounding_box; | ||||
|     // Whether or not is needed to recalculate the transformed bounding box.
 | ||||
|     mutable bool          m_transformed_bounding_box_dirty; | ||||
|     // Pointer to convex hull of the original mesh, if any.
 | ||||
|     // This object may or may not own the convex hull instance based on m_convex_hull_owned
 | ||||
|     const TriangleMesh*   m_convex_hull; | ||||
|     bool                  m_convex_hull_owned; | ||||
|     // Convex hull of the volume, if any.
 | ||||
|     std::shared_ptr<const TriangleMesh> m_convex_hull; | ||||
|     // Bounding box of this volume, in unscaled coordinates.
 | ||||
|     mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box; | ||||
|     // Whether or not is needed to recalculate the transformed convex hull bounding box.
 | ||||
|  | @ -395,7 +393,9 @@ public: | |||
|     double get_sla_shift_z() const { return m_sla_shift_z; } | ||||
|     void set_sla_shift_z(double z) { m_sla_shift_z = z; } | ||||
| 
 | ||||
|     void set_convex_hull(const TriangleMesh *convex_hull, bool owned); | ||||
|     void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); } | ||||
|     void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); } | ||||
|     void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); } | ||||
| 
 | ||||
|     int                 object_idx() const { return this->composite_id.object_id; } | ||||
|     int                 volume_idx() const { return this->composite_id.volume_id; } | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ void BackgroundSlicingProcess::process_fff() | |||
| 	    	// Perform the final post-processing of the export path by applying the print statistics over the file name.
 | ||||
| 	    	std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); | ||||
| 		    if (copy_file(m_temp_output_path, export_path) != 0) | ||||
| 	    		throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); | ||||
| 	    		throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); | ||||
| 	    	m_print->set_status(95, _utf8(L("Running post-processing scripts"))); | ||||
| 	    	run_post_process_scripts(export_path, m_fff_print->config()); | ||||
| 	    	m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); | ||||
|  |  | |||
|  | @ -30,11 +30,9 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) | |||
| 	SetMinSize(GetSize()); | ||||
| 	main_sizer->SetSizeHints(this); | ||||
| 
 | ||||
| 	// needed to actually free memory
 | ||||
| 	this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e) { | ||||
| 		EndModal(wxID_OK); | ||||
| 		Destroy(); | ||||
| 	})); | ||||
|     this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) { | ||||
|         EndModal(wxID_CANCEL); | ||||
|     })); | ||||
| } | ||||
| 
 | ||||
| void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) | ||||
|  | @ -135,7 +133,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 
 | ||||
| // Called from the constructor.
 | ||||
| // Create a panel for a rectangular / circular / custom bed shape.
 | ||||
| ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title) | ||||
| ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) | ||||
| { | ||||
| 
 | ||||
| 	auto panel = new wxPanel(m_shape_options_book); | ||||
|  | @ -305,8 +303,9 @@ void BedShapePanel::update_shape() | |||
| 		} | ||||
| 		m_canvas->m_bed_shape = points; | ||||
| 	} | ||||
|     else if (page_idx == SHAPE_CUSTOM)  | ||||
|         m_canvas->m_bed_shape = m_loaded_bed_shape; | ||||
| 
 | ||||
| //	$self->{on_change}->();
 | ||||
| 	update_preview(); | ||||
| } | ||||
| 
 | ||||
|  | @ -351,8 +350,9 @@ void BedShapePanel::load_stl() | |||
| 	std::vector<Vec2d> points; | ||||
| 	for (auto pt : polygon.points) | ||||
| 		points.push_back(unscale(pt)); | ||||
| 	m_canvas->m_bed_shape = points; | ||||
| 	update_preview(); | ||||
| 
 | ||||
|     m_loaded_bed_shape = points; | ||||
|     update_shape(); | ||||
| } | ||||
| 
 | ||||
| } // GUI
 | ||||
|  |  | |||
|  | @ -16,7 +16,8 @@ namespace GUI { | |||
| using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>; | ||||
| class BedShapePanel : public wxPanel | ||||
| { | ||||
| 	Bed_2D*			m_canvas; | ||||
| 	Bed_2D*			   m_canvas; | ||||
|     std::vector<Vec2d> m_loaded_bed_shape; | ||||
| 
 | ||||
| public: | ||||
| 	BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} | ||||
|  | @ -24,8 +25,8 @@ public: | |||
| 
 | ||||
| 	void		build_panel(ConfigOptionPoints* default_pt); | ||||
| 	 | ||||
| 	ConfigOptionsGroupShp	init_shape_options_page(wxString title); | ||||
| 	void		set_shape(ConfigOptionPoints* points); | ||||
|     ConfigOptionsGroupShp	init_shape_options_page(const wxString& title); | ||||
|     void		set_shape(ConfigOptionPoints* points); | ||||
| 	void		update_preview(); | ||||
| 	void		update_shape(); | ||||
| 	void		load_stl(); | ||||
|  |  | |||
|  | @ -56,9 +56,9 @@ public: | |||
| 
 | ||||
|     Vec3d get_dir_right() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(0); } | ||||
|     Vec3d get_dir_up() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(1); } | ||||
|     Vec3d get_dir_forward() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } | ||||
|     Vec3d get_dir_forward() const { return -m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } | ||||
| 
 | ||||
|     Vec3d get_position() const { return m_view_matrix.matrix().block(0, 3, 3, 1); } | ||||
|     Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); } | ||||
| 
 | ||||
|     void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; | ||||
|     void apply_view_matrix() const; | ||||
|  |  | |||
|  | @ -63,11 +63,6 @@ static const float GROUND_Z = -0.02f; | |||
| static const float GIZMO_RESET_BUTTON_HEIGHT = 22.0f; | ||||
| static const float GIZMO_RESET_BUTTON_WIDTH = 70.f; | ||||
| 
 | ||||
| static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, | ||||
|                                      0.0f, 1.0f, 0.0f, 0.0f, | ||||
|                                      0.0f, 0.0f, 1.0f, 0.0f, | ||||
|                                      0.0f, 0.0f, 0.0f, 1.0f }; | ||||
| 
 | ||||
| static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; | ||||
| static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; | ||||
| static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; | ||||
|  | @ -452,8 +447,7 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas | |||
| 	m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); | ||||
|     m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); | ||||
|     m_shader.set_uniform("z_cursor_band_width", band_width); | ||||
|     // The shader requires the original model coordinates when rendering to the texture, so we pass it the unit matrix
 | ||||
|     m_shader.set_uniform("volume_world_matrix", UNIT_MATRIX); | ||||
|     m_shader.set_uniform("object_max_z", m_object_max_z); | ||||
| 
 | ||||
|     glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); | ||||
|     glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); | ||||
|  | @ -466,10 +460,10 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas | |||
| 
 | ||||
|     ::glBegin(GL_QUADS); | ||||
|     ::glNormal3f(0.0f, 0.0f, 1.0f); | ||||
|     ::glVertex3f(l, b, 0.0f); | ||||
|     ::glVertex3f(r, b, 0.0f); | ||||
|     ::glVertex3f(r, t, m_object_max_z); | ||||
|     ::glVertex3f(l, t, m_object_max_z); | ||||
|     ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); | ||||
|     ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); | ||||
|     ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); | ||||
|     ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); | ||||
|     glsafe(::glEnd()); | ||||
|     glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); | ||||
| 
 | ||||
|  | @ -522,6 +516,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G | |||
|     GLint z_cursor_id                       = ::glGetUniformLocation(shader_id, "z_cursor"); | ||||
|     GLint z_cursor_band_width_id            = ::glGetUniformLocation(shader_id, "z_cursor_band_width"); | ||||
|     GLint world_matrix_id                   = ::glGetUniformLocation(shader_id, "volume_world_matrix"); | ||||
|     GLint object_max_z_id                   = ::glGetUniformLocation(shader_id, "object_max_z"); | ||||
|     glcheck(); | ||||
| 
 | ||||
|     if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1)  | ||||
|  | @ -548,7 +543,10 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G | |||
|             // Render the object using the layer editing shader and texture.
 | ||||
|             if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) | ||||
|                 continue; | ||||
|             glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast<float>().data())); | ||||
|             if (world_matrix_id != -1) | ||||
|                 glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast<float>().data())); | ||||
|             if (object_max_z_id != -1) | ||||
|                 glsafe(::glUniform1f(object_max_z_id, GLfloat(0))); | ||||
|             glvolume->render(); | ||||
|         } | ||||
|         // Revert back to the previous shader.
 | ||||
|  | @ -1210,6 +1208,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); | |||
| wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); | ||||
| 
 | ||||
| GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) | ||||
|     : m_canvas(canvas) | ||||
|  | @ -1579,7 +1578,13 @@ void GLCanvas3D::update_volumes_colors_by_extruder() | |||
| 
 | ||||
| void GLCanvas3D::render() | ||||
| { | ||||
|     wxCHECK_RET(!m_in_render, "GLCanvas3D::render() called recursively"); | ||||
|     if (m_in_render) | ||||
|     { | ||||
|         // if called recursively, return
 | ||||
|         m_dirty = true; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     m_in_render = true; | ||||
|     Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); | ||||
|     (void)in_render_guard; | ||||
|  | @ -2463,6 +2468,20 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) | |||
|                 } | ||||
|                 else if (keyCode == WXK_CONTROL) | ||||
|                     m_dirty = true; | ||||
|                 // DoubleSlider navigation in Preview
 | ||||
|                 else if (keyCode == WXK_LEFT    ||  | ||||
|                          keyCode == WXK_RIGHT   || | ||||
|                          keyCode == WXK_UP      ||  | ||||
|                          keyCode == WXK_DOWN    || | ||||
|                          keyCode == '+'         ||  | ||||
|                          keyCode == WXK_NUMPAD_ADD ||  | ||||
|                          keyCode == '-'         ||  | ||||
|                          keyCode == 390         ||  | ||||
|                          keyCode == WXK_DELETE  ||  | ||||
|                          keyCode == WXK_BACK    ) | ||||
|                 { | ||||
|                     post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -5498,7 +5517,7 @@ void GLCanvas3D::_load_sla_shells() | |||
|         v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); | ||||
|         v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); | ||||
|         v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); | ||||
|         v.set_convex_hull(new TriangleMesh(std::move(mesh.convex_hull_3d())), true); | ||||
|         v.set_convex_hull(mesh.convex_hull_3d()); | ||||
|     }; | ||||
| 
 | ||||
|     // adds objects' volumes 
 | ||||
|  |  | |||
|  | @ -124,6 +124,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); | |||
| wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); | ||||
| 
 | ||||
| class GLCanvas3D | ||||
| { | ||||
|  |  | |||
|  | @ -261,7 +261,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx / | |||
| 
 | ||||
|     const stl_stats& stats = vol_idx == -1 ? | ||||
|                             (*m_objects)[obj_idx]->get_object_stl_stats() : | ||||
|                             (*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; | ||||
|                             (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stl.stats; | ||||
| 
 | ||||
|     std::map<std::string, int> error_msg = { | ||||
|         { L("degenerate facets"),   stats.degenerate_facets }, | ||||
|  | @ -1415,13 +1415,18 @@ void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) | |||
| 
 | ||||
| void ObjectList::load_subobject(ModelVolumeType type) | ||||
| { | ||||
|     auto item = GetSelection(); | ||||
|     if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) | ||||
|     wxDataViewItem item = GetSelection(); | ||||
|     // we can add volumes for Object or Instance
 | ||||
|     if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance))) | ||||
|         return; | ||||
|     int obj_idx = m_objects_model->GetIdByItem(item); | ||||
|     const int obj_idx = m_objects_model->GetObjectIdByItem(item); | ||||
| 
 | ||||
|     if (obj_idx < 0) return; | ||||
| 
 | ||||
|     // Get object item, if Instance is selected
 | ||||
|     if (m_objects_model->GetItemType(item)&itInstance) | ||||
|         item = m_objects_model->GetItemById(obj_idx); | ||||
| 
 | ||||
|     std::vector<std::pair<wxString, bool>> volumes_info; | ||||
|     load_part((*m_objects)[obj_idx], volumes_info, type); | ||||
| 
 | ||||
|  | @ -1592,7 +1597,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode | |||
|         // First (any) GLVolume of the selected instance. They all share the same instance matrix.
 | ||||
|         const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         // Transform the new modifier to be aligned with the print bed.
 | ||||
| 		const BoundingBoxf3 mesh_bb = new_volume->mesh.bounding_box(); | ||||
| 		const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); | ||||
| 		new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); | ||||
|         // Set the modifier position.
 | ||||
|         auto offset = (type_name == "Slab") ? | ||||
|  | @ -2150,9 +2155,11 @@ void ObjectList::update_selections() | |||
|     if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) | ||||
|     { | ||||
|         const auto item = GetSelection(); | ||||
|         if (selection.is_single_full_object() &&  | ||||
|             m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) | ||||
|             return;  | ||||
|         if (selection.is_single_full_object()) { | ||||
|             if ( m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) | ||||
|                 return; | ||||
|             sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); | ||||
|         } | ||||
|         if (selection.is_single_volume() || selection.is_any_modifier()) { | ||||
|             const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|             if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) | ||||
|  |  | |||
|  | @ -92,6 +92,7 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo) | |||
|     combo->SetValue(selection); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ObjectManipulation::ObjectManipulation(wxWindow* parent) : | ||||
|     OG_Settings(parent, true) | ||||
| #ifndef __APPLE__ | ||||
|  | @ -162,16 +163,71 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
| 
 | ||||
|     const int field_width = 5; | ||||
| 
 | ||||
|     // Mirror button size:
 | ||||
|     const int mirror_btn_width = 3; | ||||
| 
 | ||||
|     // Legend for object modification
 | ||||
|     line = Line{ "", "" }; | ||||
|     def.label = ""; | ||||
|     def.type = coString; | ||||
|     def.width = field_width/*50*/; | ||||
|     def.width = field_width - mirror_btn_width;//field_width/*50*/;
 | ||||
| 
 | ||||
|     // Load bitmaps to be used for the mirroring buttons:
 | ||||
|     m_mirror_bitmap_on  = ScalableBitmap(parent, "mirroring_on.png"); | ||||
|     m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png"); | ||||
|     m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); | ||||
| 
 | ||||
| 	for (const std::string axis : { "x", "y", "z" }) { | ||||
|         const std::string label = boost::algorithm::to_upper_copy(axis); | ||||
|         def.set_default_value(new ConfigOptionString{ "   " + label }); | ||||
|         Option option = Option(def, axis + "_axis_legend"); | ||||
| 
 | ||||
|         unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
 | ||||
| 
 | ||||
|         // We will add a button to toggle mirroring to each axis:
 | ||||
|         auto mirror_button = [=](wxWindow* parent) { | ||||
|             wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); | ||||
|             auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); | ||||
|             btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); | ||||
| 
 | ||||
|             m_mirror_buttons[axis_idx].first = btn; | ||||
|             m_mirror_buttons[axis_idx].second = mbShown; | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(btn); | ||||
| 
 | ||||
|             btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                 Axis axis = (Axis)(axis_idx + X); | ||||
|                 if (m_mirror_buttons[axis_idx].second == mbHidden) | ||||
|                     return; | ||||
| 
 | ||||
|                 GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|                 Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|                 if (selection.is_single_volume() || selection.is_single_modifier()) { | ||||
|                     GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin())); | ||||
|                     volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); | ||||
|                 } | ||||
|                 else if (selection.is_single_full_instance()) { | ||||
|                     for (unsigned int idx : selection.get_volume_idxs()){ | ||||
|                         GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx)); | ||||
|                         volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                     return; | ||||
| 
 | ||||
|                 // Update mirroring at the GLVolumes.
 | ||||
|                 selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); | ||||
|                 selection.synchronize_unselected_volumes(); | ||||
|                 // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
 | ||||
|                 canvas->do_mirror(); | ||||
|                 canvas->set_as_dirty(); | ||||
|                 UpdateAndShow(true); | ||||
|             }); | ||||
|         return sizer; | ||||
|         }; | ||||
| 
 | ||||
|         option.side_widget = mirror_button; | ||||
|         line.append_option(option); | ||||
|     } | ||||
|     line.near_label_widget = [this](wxWindow* parent) { | ||||
|  | @ -190,8 +246,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|         def.set_default_value(new ConfigOptionFloat(0.0)); | ||||
|         def.width = field_width/*50*/; | ||||
| 
 | ||||
|         // Add "uniform scaling" button in front of "Scale" option 
 | ||||
|         if (option_name == "Scale") { | ||||
|             // Add "uniform scaling" button in front of "Scale" option
 | ||||
|             line.near_label_widget = [this](wxWindow* parent) { | ||||
|                 auto btn = new LockButton(parent, wxID_ANY); | ||||
|                 btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){ | ||||
|  | @ -201,8 +257,59 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|                 m_lock_bnt = btn; | ||||
|                 return btn; | ||||
|             }; | ||||
|             // Add reset scale button
 | ||||
|             auto reset_scale_button = [=](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); | ||||
|                 btn->SetToolTip(_(L("Reset scale"))); | ||||
|                 m_reset_scale_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                     change_scale_value(0, 100.); | ||||
|                     change_scale_value(1, 100.); | ||||
|                     change_scale_value(2, 100.); | ||||
|                 }); | ||||
|             return sizer; | ||||
|             }; | ||||
|             line.append_widget(reset_scale_button); | ||||
|         } | ||||
|         else if (option_name == "Rotation") { | ||||
|             // Add reset rotation button
 | ||||
|             auto reset_rotation_button = [=](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); | ||||
|                 btn->SetToolTip(_(L("Reset rotation"))); | ||||
|                 m_reset_rotation_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|                     Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|                     if (selection.is_single_volume() || selection.is_single_modifier()) { | ||||
|                         GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin())); | ||||
|                         volume->set_volume_rotation(Vec3d::Zero()); | ||||
|                     } | ||||
|                     else if (selection.is_single_full_instance()) { | ||||
|                         for (unsigned int idx : selection.get_volume_idxs()){ | ||||
|                             GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx)); | ||||
|                             volume->set_instance_rotation(Vec3d::Zero()); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                         return; | ||||
| 
 | ||||
|                     // Update rotation at the GLVolumes.
 | ||||
|                     selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); | ||||
|                     selection.synchronize_unselected_volumes(); | ||||
|                     // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
 | ||||
|                     canvas->do_rotate(); | ||||
| 
 | ||||
|                     UpdateAndShow(true); | ||||
|                 }); | ||||
|                 return sizer; | ||||
|             }; | ||||
|             line.append_widget(reset_rotation_button); | ||||
|         } | ||||
|         // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
 | ||||
|         else if (option_name == "Size") { | ||||
|             line.near_label_widget = [this](wxWindow* parent) { | ||||
|  | @ -224,8 +331,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|         return line; | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     // Settings table
 | ||||
|     m_og->sidetext_width = 3; | ||||
|     m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); | ||||
|     m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label); | ||||
|     m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label); | ||||
|  | @ -239,6 +346,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|         ctrl->msw_rescale(); | ||||
|     }; | ||||
| } | ||||
|   | ||||
|   | ||||
| 
 | ||||
| void ObjectManipulation::Show(const bool show) | ||||
| { | ||||
|  | @ -408,9 +517,95 @@ void ObjectManipulation::update_if_dirty() | |||
|     else | ||||
|         m_og->disable(); | ||||
| 
 | ||||
|     update_reset_buttons_visibility(); | ||||
|     update_mirror_buttons_visibility(); | ||||
| 
 | ||||
|     m_dirty = false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void ObjectManipulation::update_reset_buttons_visibility() | ||||
| { | ||||
|     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|     if (!canvas) | ||||
|         return; | ||||
|     const Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|     bool show_rotation = false; | ||||
|     bool show_scale = false; | ||||
| 
 | ||||
|     if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { | ||||
|         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         Vec3d rotation; | ||||
|         Vec3d scale; | ||||
| 
 | ||||
|         if (selection.is_single_full_instance()) { | ||||
|             rotation = volume->get_instance_rotation(); | ||||
|             scale = volume->get_instance_scaling_factor(); | ||||
|         } | ||||
|         else { | ||||
|             rotation = volume->get_volume_rotation(); | ||||
|             scale = volume->get_volume_scaling_factor(); | ||||
|         } | ||||
|         show_rotation = !rotation.isApprox(Vec3d::Zero()); | ||||
|         show_scale = !scale.isApprox(Vec3d::Ones()); | ||||
|     } | ||||
| 
 | ||||
|     wxGetApp().CallAfter([this, show_rotation, show_scale]{ | ||||
|         m_reset_rotation_button->Show(show_rotation); | ||||
|         m_reset_scale_button->Show(show_scale); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void ObjectManipulation::update_mirror_buttons_visibility() | ||||
| { | ||||
|     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|     Selection& selection = canvas->get_selection(); | ||||
|     std::array<MirrorButtonState, 3> new_states = {mbHidden, mbHidden, mbHidden}; | ||||
| 
 | ||||
|     if (!m_world_coordinates) { | ||||
|         if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { | ||||
|             const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|             Vec3d mirror; | ||||
| 
 | ||||
|             if (selection.is_single_full_instance()) | ||||
|                 mirror = volume->get_instance_mirror(); | ||||
|             else | ||||
|                 mirror = volume->get_volume_mirror(); | ||||
| 
 | ||||
|             for (unsigned char i=0; i<3; ++i) | ||||
|                 new_states[i] = (mirror[i] < 0. ? mbActive : mbShown); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         // the mirroring buttons should be hidden in world coordinates,
 | ||||
|         // unless we make it actually mirror in world coords.
 | ||||
|     } | ||||
| 
 | ||||
|     // Hiding the buttons through Hide() always messed up the sizers. As a workaround, the button
 | ||||
|     // is assigned a transparent bitmap. We must of course remember the actual state.
 | ||||
|     wxGetApp().CallAfter([this, new_states]{ | ||||
|         for (int i=0; i<3; ++i) { | ||||
|             if (new_states[i] != m_mirror_buttons[i].second) { | ||||
|                 const wxBitmap* bmp; | ||||
|                 switch (new_states[i]) { | ||||
|                     case mbHidden : bmp = &m_mirror_bitmap_hidden.bmp(); m_mirror_buttons[i].first->Enable(false); break; | ||||
|                     case mbShown  : bmp = &m_mirror_bitmap_off.bmp(); m_mirror_buttons[i].first->Enable(true); break; | ||||
|                     case mbActive : bmp = &m_mirror_bitmap_on.bmp(); m_mirror_buttons[i].first->Enable(true); break; | ||||
|                 } | ||||
|                 m_mirror_buttons[i].first->SetBitmap(*bmp); | ||||
|                 m_mirror_buttons[i].second = new_states[i]; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
| void ObjectManipulation::emulate_kill_focus() | ||||
| { | ||||
|  | @ -493,7 +688,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) | |||
| 
 | ||||
|     m_cache.rotation = rotation; | ||||
| 	m_cache.rotation_rounded(axis) = DBL_MAX; | ||||
| 	this->UpdateAndShow(true); | ||||
|     this->UpdateAndShow(true); | ||||
| } | ||||
| 
 | ||||
| void ObjectManipulation::change_scale_value(int axis, double value) | ||||
|  | @ -511,6 +706,7 @@ void ObjectManipulation::change_scale_value(int axis, double value) | |||
| 	this->UpdateAndShow(true); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ObjectManipulation::change_size_value(int axis, double value) | ||||
| { | ||||
|     if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) | ||||
|  | @ -666,6 +862,12 @@ void ObjectManipulation::msw_rescale() | |||
|     m_manifold_warning_bmp.msw_rescale(); | ||||
|     m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); | ||||
| 
 | ||||
|     m_mirror_bitmap_on.msw_rescale(); | ||||
|     m_mirror_bitmap_off.msw_rescale(); | ||||
|     m_mirror_bitmap_hidden.msw_rescale(); | ||||
|     m_reset_scale_button->msw_rescale(); | ||||
|     m_reset_rotation_button->msw_rescale(); | ||||
| 
 | ||||
|     get_og()->msw_rescale(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -53,6 +53,23 @@ class ObjectManipulation : public OG_Settings | |||
|     wxStaticText*   m_scale_Label = nullptr; | ||||
|     wxStaticText*   m_rotate_Label = nullptr; | ||||
| 
 | ||||
|     // Non-owning pointers to the reset buttons, so we can hide and show them.
 | ||||
|     ScalableButton* m_reset_scale_button = nullptr; | ||||
|     ScalableButton* m_reset_rotation_button = nullptr; | ||||
| 
 | ||||
|     // Mirroring buttons and their current state
 | ||||
|     enum MirrorButtonState { | ||||
|         mbHidden, | ||||
|         mbShown, | ||||
|         mbActive | ||||
|     }; | ||||
|     std::array<std::pair<ScalableButton*, MirrorButtonState>, 3> m_mirror_buttons; | ||||
| 
 | ||||
|     // Bitmaps for the mirroring buttons.
 | ||||
|     ScalableBitmap m_mirror_bitmap_on; | ||||
|     ScalableBitmap m_mirror_bitmap_off; | ||||
|     ScalableBitmap m_mirror_bitmap_hidden; | ||||
| 
 | ||||
|     // Needs to be updated from OnIdle?
 | ||||
|     bool            m_dirty = false; | ||||
|     // Cached labels for the delayed update, not localized!
 | ||||
|  | @ -111,10 +128,10 @@ private: | |||
|     void reset_settings_value(); | ||||
|     void update_settings_value(const Selection& selection); | ||||
| 
 | ||||
|     // update size values after scale unit changing or "gizmos"
 | ||||
|     void update_size_value(const Vec3d& size); | ||||
|     // update rotation value after "gizmos"
 | ||||
|     void update_rotation_value(const Vec3d& rotation); | ||||
|     // Show or hide scale/rotation reset buttons if needed
 | ||||
|     void update_reset_buttons_visibility(); | ||||
|     //Show or hide mirror buttons
 | ||||
|     void update_mirror_buttons_visibility(); | ||||
| 
 | ||||
|     // change values 
 | ||||
|     void change_position_value(int axis, double value); | ||||
|  |  | |||
|  | @ -414,6 +414,12 @@ void Preview::msw_rescale() | |||
|     refresh_print(); | ||||
| } | ||||
| 
 | ||||
| void Preview::move_double_slider(wxKeyEvent& evt) | ||||
| { | ||||
|     if (m_slider)  | ||||
|         m_slider->OnKeyDown(evt); | ||||
| } | ||||
| 
 | ||||
| void Preview::bind_event_handlers() | ||||
| { | ||||
|     this->Bind(wxEVT_SIZE, &Preview::on_size, this); | ||||
|  |  | |||
|  | @ -122,6 +122,7 @@ public: | |||
|     void refresh_print(); | ||||
| 
 | ||||
|     void msw_rescale(); | ||||
|     void move_double_slider(wxKeyEvent& evt); | ||||
| 
 | ||||
| private: | ||||
|     bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_i | |||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_quadric(nullptr) | ||||
|     , m_its(nullptr) | ||||
| { | ||||
|     m_quadric = ::gluNewQuadric(); | ||||
|     if (m_quadric != nullptr) | ||||
|  | @ -379,36 +380,23 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const | |||
| bool GLGizmoSlaSupports::is_mesh_update_necessary() const | ||||
| { | ||||
|     return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) | ||||
|         && ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0); | ||||
|         && ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr); | ||||
| } | ||||
| 
 | ||||
| void GLGizmoSlaSupports::update_mesh() | ||||
| { | ||||
|     wxBusyCursor wait; | ||||
|     Eigen::MatrixXf& V = m_V; | ||||
|     Eigen::MatrixXi& F = m_F; | ||||
|     // We rely on SLA model object having a single volume,
 | ||||
|     // this way we can use that mesh directly.
 | ||||
|     // This mesh does not account for the possible Z up SLA offset.
 | ||||
|     m_mesh = &m_model_object->volumes.front()->mesh; | ||||
|     const_cast<TriangleMesh*>(m_mesh)->require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     const stl_file& stl = m_mesh->stl; | ||||
|     V.resize(3 * stl.stats.number_of_facets, 3); | ||||
|     F.resize(stl.stats.number_of_facets, 3); | ||||
|     for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) { | ||||
|         const stl_facet* facet = stl.facet_start+i; | ||||
|         V(3*i+0, 0) = facet->vertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); | ||||
|         V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); | ||||
|         V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); | ||||
|         F(i, 0) = 3*i+0; | ||||
|         F(i, 1) = 3*i+1; | ||||
|         F(i, 2) = 3*i+2; | ||||
|     } | ||||
|     m_mesh = &m_model_object->volumes.front()->mesh(); | ||||
|     m_its = &m_mesh->its; | ||||
|     m_current_mesh_model_id = m_model_object->id(); | ||||
|     m_editing_mode = false; | ||||
| 
 | ||||
|     m_AABB = igl::AABB<Eigen::MatrixXf,3>(); | ||||
|     m_AABB.init(m_V, m_F); | ||||
| 	m_AABB.deinit(); | ||||
|     m_AABB.init( | ||||
|         MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), | ||||
|         MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); | ||||
| } | ||||
| 
 | ||||
| // Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
 | ||||
|  | @ -416,7 +404,7 @@ void GLGizmoSlaSupports::update_mesh() | |||
| std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) | ||||
| { | ||||
|     // if the gizmo doesn't have the V, F structures for igl, calculate them first:
 | ||||
|     if (m_V.size() == 0) | ||||
|     if (m_its == nullptr) | ||||
|         update_mesh(); | ||||
| 
 | ||||
|     const Camera& camera = m_parent.get_camera(); | ||||
|  | @ -442,7 +430,10 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse | |||
|     point1 = inv * point1; | ||||
|     point2 = inv * point2; | ||||
| 
 | ||||
|     if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hits)) | ||||
|     if (!m_AABB.intersect_ray( | ||||
|         MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), | ||||
|         MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), | ||||
|         point1.cast<float>(), (point2-point1).cast<float>(), hits)) | ||||
|         throw std::invalid_argument("unproject_on_mesh(): No intersection found."); | ||||
| 
 | ||||
|     std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); | ||||
|  | @ -457,9 +448,9 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse | |||
|         igl::Hit& hit = hits[i]; | ||||
|         int fid = hit.id;   // facet id
 | ||||
|         bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
 | ||||
|         a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); | ||||
|         b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); | ||||
|         result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); | ||||
|         a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); | ||||
|         b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); | ||||
|         result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; | ||||
|         if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>())) | ||||
|             break; | ||||
|     } | ||||
|  | @ -550,7 +541,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|             const Selection& selection = m_parent.get_selection(); | ||||
|             const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|             const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); | ||||
|             Vec3f direction_to_camera = camera.get_dir_forward().cast<float>(); | ||||
|             Vec3f direction_to_camera = -camera.get_dir_forward().cast<float>(); | ||||
|             Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval(); | ||||
|             Vec3f scaling = volume->get_instance_scaling_factor().cast<float>(); | ||||
|             direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); | ||||
|  | @ -564,15 +555,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|                     // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | ||||
|                     std::vector<igl::Hit> hits; | ||||
|                     // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
 | ||||
|                     if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { | ||||
|                     if (m_AABB.intersect_ray( | ||||
|                             MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), | ||||
|                             MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), | ||||
|                             support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { | ||||
|                         std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); | ||||
| 
 | ||||
|                         if (m_clipping_plane_distance != 0.f) { | ||||
|                             // If the closest hit facet normal points in the same direction as the ray,
 | ||||
|                             // we are looking through the mesh and should therefore discard the point:
 | ||||
|                             int fid = hits.front().id;   // facet id
 | ||||
|                             Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); | ||||
|                             Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); | ||||
|                             Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); | ||||
|                             Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); | ||||
|                             if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) | ||||
|                                 is_obscured = true; | ||||
| 
 | ||||
|  | @ -582,7 +576,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|                                 int fid = hit.id;   // facet id
 | ||||
| 
 | ||||
|                                 Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
 | ||||
|                                 Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); | ||||
|                                 Vec3f hit_pos = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; | ||||
|                                 if (is_point_clipped(hit_pos.cast<double>())) { | ||||
|                                     hits.erase(hits.begin()+j); | ||||
|                                     --j; | ||||
|  | @ -759,9 +753,12 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const | |||
|     int idx = 0; | ||||
|     Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos; | ||||
|     Eigen::Matrix<float, 1, 3> cc; | ||||
|     m_AABB.squared_distance(m_V, m_F, pp, idx, cc); | ||||
|     Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); | ||||
|     Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); | ||||
|     m_AABB.squared_distance( | ||||
|         MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), | ||||
|         MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), | ||||
|         pp, idx, cc); | ||||
|     Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]); | ||||
|     Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]); | ||||
|     m_editing_mode_cache[i].normal = a.cross(b); | ||||
| } | ||||
| 
 | ||||
|  | @ -1067,8 +1064,7 @@ void GLGizmoSlaSupports::on_set_state() | |||
|                 m_clipping_plane_distance = 0.f; | ||||
|                 // Release triangle mesh slicer and the AABB spatial search structure.
 | ||||
|                 m_AABB.deinit(); | ||||
| 				m_V = Eigen::MatrixXf(); | ||||
| 				m_F = Eigen::MatrixXi(); | ||||
|                 m_its = nullptr; | ||||
|                 m_tms.reset(); | ||||
|                 m_supports_tms.reset(); | ||||
|             }); | ||||
|  |  | |||
|  | @ -35,10 +35,11 @@ private: | |||
|     const float RenderPointScale = 1.f; | ||||
| 
 | ||||
|     GLUquadricObj* m_quadric; | ||||
|     Eigen::MatrixXf m_V; // vertices
 | ||||
|     Eigen::MatrixXi m_F; // facets indices
 | ||||
|     igl::AABB<Eigen::MatrixXf,3> m_AABB; | ||||
|     typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned; | ||||
|     typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned; | ||||
|     igl::AABB<MapMatrixXfUnaligned, 3> m_AABB; | ||||
|     const TriangleMesh* m_mesh; | ||||
|     const indexed_triangle_set* m_its; | ||||
|     mutable const TriangleMesh* m_supports_mesh; | ||||
|     mutable std::vector<Vec2f> m_triangles; | ||||
|     mutable std::vector<Vec2f> m_supports_triangles; | ||||
|  | @ -131,6 +132,11 @@ private: | |||
| 
 | ||||
| protected: | ||||
|     void on_set_state() override; | ||||
|     virtual void on_set_hover_id() | ||||
|     { | ||||
|         if ((int)m_editing_mode_cache.size() <= m_hover_id) | ||||
|             m_hover_id = -1; | ||||
|     } | ||||
|     void on_start_dragging(const Selection& selection) override; | ||||
|     virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S | |||
| #endif // _WIN32
 | ||||
| 
 | ||||
| 	// initialize status bar
 | ||||
| 	m_statusbar = new ProgressStatusBar(this); | ||||
| 	m_statusbar.reset(new ProgressStatusBar(this)); | ||||
| 	m_statusbar->embed(this); | ||||
|     m_statusbar->set_status_text(_(L("Version")) + " " + | ||||
| 		SLIC3R_VERSION + | ||||
|  | @ -103,6 +103,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S | |||
|             event.Veto(); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if(m_plater) m_plater->stop_jobs(); | ||||
| 
 | ||||
|         // Weird things happen as the Paint messages are floating around the windows being destructed.
 | ||||
|         // Avoid the Paint messages by hiding the main window.
 | ||||
|  | @ -138,6 +140,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S | |||
|     update_ui_from_settings();    // FIXME (?)
 | ||||
| } | ||||
| 
 | ||||
| MainFrame::~MainFrame() = default; | ||||
| 
 | ||||
| void MainFrame::update_title() | ||||
| { | ||||
|     wxString title = wxEmptyString; | ||||
|  | @ -976,7 +980,8 @@ void MainFrame::load_config(const DynamicPrintConfig& config) | |||
| 				if (! boost::algorithm::ends_with(opt_key, "_settings_id")) | ||||
| 					tab->get_config()->option(opt_key)->set(config.option(opt_key)); | ||||
|         } | ||||
| 	wxGetApp().load_current_presets(); | ||||
|      | ||||
|     wxGetApp().load_current_presets(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ protected: | |||
| 
 | ||||
| public: | ||||
|     MainFrame(); | ||||
|     ~MainFrame() {} | ||||
|     ~MainFrame(); | ||||
| 
 | ||||
|     Plater*     plater() { return m_plater; } | ||||
| 
 | ||||
|  | @ -126,7 +126,7 @@ public: | |||
|     Plater*             m_plater { nullptr }; | ||||
|     wxNotebook*         m_tabpanel { nullptr }; | ||||
|     wxProgressDialog*   m_progress_dialog { nullptr }; | ||||
|     ProgressStatusBar*  m_statusbar { nullptr }; | ||||
|     std::unique_ptr<ProgressStatusBar>  m_statusbar; | ||||
| }; | ||||
| 
 | ||||
| } // GUI
 | ||||
|  |  | |||
|  | @ -276,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	full_Label/* = n | |||
| 		// add sidetext if any
 | ||||
| 		if (option.sidetext != "") { | ||||
| 			auto sidetext = new wxStaticText(	this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,  | ||||
| 												/*wxSize(sidetext_width*wxGetApp().em_unit(), -1)*/wxDefaultSize, wxALIGN_LEFT); | ||||
| 												wxSize(sidetext_width != -1 ? sidetext_width*wxGetApp().em_unit() : -1, -1) /*wxDefaultSize*/, wxALIGN_LEFT); | ||||
| 			sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT); | ||||
|             sidetext->SetFont(wxGetApp().normal_font()); | ||||
| 			sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); | ||||
|  |  | |||
|  | @ -5,9 +5,11 @@ | |||
| #include <vector> | ||||
| #include <string> | ||||
| #include <regex> | ||||
| #include <future> | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include <boost/optional.hpp> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
|  | @ -1258,8 +1260,243 @@ struct Plater::priv | |||
|     Preview *preview; | ||||
| 
 | ||||
|     BackgroundSlicingProcess    background_process; | ||||
|     bool                        arranging; | ||||
|     bool                        rotoptimizing; | ||||
|      | ||||
|     // A class to handle UI jobs like arranging and optimizing rotation.
 | ||||
|     // These are not instant jobs, the user has to be informed about their
 | ||||
|     // state in the status progress indicator. On the other hand they are 
 | ||||
|     // separated from the background slicing process. Ideally, these jobs should
 | ||||
|     // run when the background process is not running.
 | ||||
|     //
 | ||||
|     // TODO: A mechanism would be useful for blocking the plater interactions:
 | ||||
|     // objects would be frozen for the user. In case of arrange, an animation
 | ||||
|     // could be shown, or with the optimize orientations, partial results
 | ||||
|     // could be displayed.
 | ||||
|     class Job: public wxEvtHandler { | ||||
|         int m_range = 100; | ||||
|         std::future<void> m_ftr; | ||||
|         priv *m_plater = nullptr; | ||||
|         std::atomic<bool> m_running {false}, m_canceled {false}; | ||||
|         bool m_finalized = false; | ||||
|          | ||||
|         void run() {  | ||||
|             m_running.store(true); process(); m_running.store(false);  | ||||
|              | ||||
|             // ensure to call the last status to finalize the job
 | ||||
|             update_status(status_range(), ""); | ||||
|         } | ||||
|          | ||||
|     protected: | ||||
|          | ||||
|         // status range for a particular job
 | ||||
|         virtual int status_range() const { return 100; } | ||||
|          | ||||
|         // status update, to be used from the work thread (process() method)
 | ||||
|         void update_status(int st, const wxString& msg = "") {  | ||||
|             auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); | ||||
|             wxQueueEvent(this, evt);  | ||||
|         } | ||||
|          | ||||
|         priv& plater() { return *m_plater; } | ||||
|         bool was_canceled() const { return m_canceled.load(); } | ||||
|          | ||||
|         // Launched just before start(), a job can use it to prepare internals
 | ||||
|         virtual void prepare() {} | ||||
|          | ||||
|         // Launched when the job is finished. It refreshes the 3dscene by def.
 | ||||
|         virtual void finalize() { | ||||
|             // Do a full refresh of scene tree, including regenerating
 | ||||
|             // all the GLVolumes. FIXME The update function shall just
 | ||||
|             // reload the modified matrices.
 | ||||
|             if(! was_canceled()) | ||||
|                 plater().update(true); | ||||
|         } | ||||
|          | ||||
|     public: | ||||
|          | ||||
|         Job(priv *_plater): m_plater(_plater) | ||||
|         { | ||||
|             Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ | ||||
|                 auto msg = evt.GetString(); | ||||
|                 if(! msg.empty()) plater().statusbar()->set_status_text(msg); | ||||
|                  | ||||
|                 if(m_finalized) return; | ||||
|                  | ||||
|                 plater().statusbar()->set_progress(evt.GetInt()); | ||||
|                 if(evt.GetInt() == status_range()) { | ||||
|                      | ||||
|                     // set back the original range and cancel callback
 | ||||
|                     plater().statusbar()->set_range(m_range); | ||||
|                     plater().statusbar()->set_cancel_callback(); | ||||
|                     wxEndBusyCursor(); | ||||
|                      | ||||
|                     finalize(); | ||||
|                      | ||||
|                     // dont do finalization again for the same process
 | ||||
|                     m_finalized = true; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         // TODO: use this when we all migrated to VS2019
 | ||||
|         // Job(const Job&) = delete;
 | ||||
|         // Job(Job&&) = default;
 | ||||
|         // Job& operator=(const Job&) = delete;
 | ||||
|         // Job& operator=(Job&&) = default;
 | ||||
|         Job(const Job&) = delete; | ||||
|         Job& operator=(const Job&) = delete; | ||||
|         Job(Job &&o) : | ||||
|             m_range(o.m_range), | ||||
|             m_ftr(std::move(o.m_ftr)), | ||||
|             m_plater(o.m_plater), | ||||
|             m_finalized(o.m_finalized) | ||||
|         { | ||||
|             m_running.store(o.m_running.load()); | ||||
|             m_canceled.store(o.m_canceled.load()); | ||||
|         } | ||||
|          | ||||
|         virtual void process() = 0; | ||||
|          | ||||
|         void start() { // Start the job. No effect if the job is already running
 | ||||
|             if(! m_running.load()) { | ||||
|                  | ||||
|                 prepare();                 | ||||
|                  | ||||
|                 // Save the current status indicatior range and push the new one
 | ||||
|                 m_range = plater().statusbar()->get_range(); | ||||
|                 plater().statusbar()->set_range(status_range()); | ||||
|                  | ||||
|                 // init cancellation flag and set the cancel callback
 | ||||
|                 m_canceled.store(false); | ||||
|                 plater().statusbar()->set_cancel_callback( [this](){  | ||||
|                     m_canceled.store(true); | ||||
|                 }); | ||||
|                  | ||||
|                 m_finalized = false; | ||||
|                  | ||||
|                 // Changing cursor to busy
 | ||||
|                 wxBeginBusyCursor(); | ||||
|                  | ||||
|                 try {   // Execute the job
 | ||||
|                     m_ftr = std::async(std::launch::async, &Job::run, this); | ||||
|                 } catch(std::exception& ) {  | ||||
|                     update_status(status_range(),  | ||||
|                     _(L("ERROR: not enough resources to execute a new job."))); | ||||
|                 } | ||||
|                  | ||||
|                 // The state changes will be undone when the process hits the
 | ||||
|                 // last status value, in the status update handler (see ctor)
 | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // To wait for the running job and join the threads. False is returned
 | ||||
|         // if the timeout has been reached and the job is still running. Call
 | ||||
|         // cancel() before this fn if you want to explicitly end the job.
 | ||||
|         bool join(int timeout_ms = 0) const {  | ||||
|             if(!m_ftr.valid()) return true; | ||||
|              | ||||
|             if(timeout_ms <= 0)  | ||||
|                 m_ftr.wait(); | ||||
|             else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==  | ||||
|                     std::future_status::timeout)  | ||||
|                 return false; | ||||
|              | ||||
|             return true; | ||||
|         } | ||||
|          | ||||
|         bool is_running() const { return m_running.load(); } | ||||
|         void cancel() { m_canceled.store(true); } | ||||
|     }; | ||||
|      | ||||
|     enum class Jobs : size_t { | ||||
|         Arrange, | ||||
|         Rotoptimize | ||||
|     }; | ||||
|      | ||||
|     // Jobs defined inside the group class will be managed so that only one can
 | ||||
|     // run at a time. Also, the background process will be stopped if a job is
 | ||||
|     // started.
 | ||||
|     class ExclusiveJobGroup { | ||||
|          | ||||
|         static const int ABORT_WAIT_MAX_MS = 10000; | ||||
|          | ||||
|         priv * m_plater; | ||||
| 
 | ||||
|         class ArrangeJob : public Job | ||||
|         { | ||||
|             int count = 0; | ||||
| 
 | ||||
|         protected: | ||||
|             void prepare() override | ||||
|             { | ||||
|                 count = 0; | ||||
|                 for (auto obj : plater().model.objects) | ||||
|                     count += int(obj->instances.size()); | ||||
|             } | ||||
| 
 | ||||
|         public: | ||||
|             //using Job::Job;
 | ||||
|             ArrangeJob(priv * pltr): Job(pltr) {} | ||||
|             int  status_range() const override { return count; } | ||||
|             void set_count(int c) { count = c; } | ||||
|             void process() override; | ||||
|         } arrange_job/*{m_plater}*/; | ||||
| 
 | ||||
|         class RotoptimizeJob : public Job | ||||
|         { | ||||
|         public: | ||||
|             //using Job::Job;
 | ||||
|             RotoptimizeJob(priv * pltr): Job(pltr) {} | ||||
|             void process() override; | ||||
|         } rotoptimize_job/*{m_plater}*/; | ||||
| 
 | ||||
|         // To create a new job, just define a new subclass of Job, implement
 | ||||
|         // the process and the optional prepare() and finalize() methods
 | ||||
|         // Register the instance of the class in the m_jobs container
 | ||||
|         // if it cannot run concurrently with other jobs in this group 
 | ||||
| 
 | ||||
|         std::vector<std::reference_wrapper<Job>> m_jobs/*{arrange_job,
 | ||||
|                                                         rotoptimize_job}*/; | ||||
| 
 | ||||
|     public: | ||||
|         ExclusiveJobGroup(priv *_plater) | ||||
|             : m_plater(_plater) | ||||
|             , arrange_job(m_plater) | ||||
|             , rotoptimize_job(m_plater) | ||||
|             , m_jobs({arrange_job, rotoptimize_job}) | ||||
|         {} | ||||
| 
 | ||||
|         void start(Jobs jid) { | ||||
|             m_plater->background_process.stop(); | ||||
|             stop_all(); | ||||
|             m_jobs[size_t(jid)].get().start(); | ||||
|         } | ||||
|          | ||||
|         void cancel_all() { for (Job& j : m_jobs) j.cancel(); } | ||||
| 
 | ||||
|         void join_all(int wait_ms = 0) | ||||
|         { | ||||
|             std::vector<bool> aborted(m_jobs.size(), false); | ||||
|              | ||||
|             for (size_t jid = 0; jid < m_jobs.size(); ++jid) | ||||
|                 aborted[jid] = m_jobs[jid].get().join(wait_ms); | ||||
| 
 | ||||
|             if (!all_of(aborted)) | ||||
|                 BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; | ||||
|         } | ||||
|          | ||||
|         void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } | ||||
|          | ||||
|         const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } | ||||
| 
 | ||||
|         bool is_any_running() const | ||||
|         { | ||||
|             return std::any_of(m_jobs.begin(), | ||||
|                                m_jobs.end(), | ||||
|                                [](const Job &j) { return j.is_running(); }); | ||||
|         } | ||||
|          | ||||
|     } m_ui_jobs{this}; | ||||
| 
 | ||||
|     bool                        delayed_scene_refresh; | ||||
|     std::string                 delayed_error_message; | ||||
| 
 | ||||
|  | @ -1434,8 +1671,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
| { | ||||
| 	this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
| 
 | ||||
|     arranging = false; | ||||
|     rotoptimizing = false; | ||||
|     background_process.set_fff_print(&fff_print); | ||||
| 	background_process.set_sla_print(&sla_print); | ||||
|     background_process.set_gcode_preview_data(&gcode_preview_data); | ||||
|  | @ -1522,6 +1757,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); | ||||
|     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); }); | ||||
|     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); | ||||
|     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); | ||||
| 
 | ||||
|     q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); | ||||
|     q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); | ||||
|  | @ -1609,7 +1845,7 @@ void Plater::priv::update_ui_from_settings() | |||
| 
 | ||||
| ProgressStatusBar* Plater::priv::statusbar() | ||||
| { | ||||
|     return main_frame->m_statusbar; | ||||
|     return main_frame->m_statusbar.get(); | ||||
| } | ||||
| 
 | ||||
| std::string Plater::priv::get_config(const std::string &key) const | ||||
|  | @ -1675,6 +1911,22 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|                     if (load_config && !config_loaded.empty()) { | ||||
|                         // Based on the printer technology field found in the loaded config, select the base for the config,
 | ||||
| 					    PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); | ||||
| 
 | ||||
|                         // We can't to load SLA project if there is at least one multi-part object on the bed
 | ||||
|                         if (printer_technology == ptSLA) | ||||
|                         { | ||||
|                             const ModelObjectPtrs& objects = q->model().objects; | ||||
|                             for (auto object : objects) | ||||
|                                 if (object->volumes.size() > 1) | ||||
|                                 { | ||||
|                                     Slic3r::GUI::show_info(nullptr, | ||||
|                                         _(L("You can't to load SLA project if there is at least one multi-part object on the bed")) + "\n\n" + | ||||
|                                         _(L("Please check your object list before preset changing.")), | ||||
|                                         _(L("Attention!"))); | ||||
|                                     return obj_idxs; | ||||
|                                 } | ||||
|                         } | ||||
| 
 | ||||
| 					    config.apply(printer_technology == ptFFF ? | ||||
|                             static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :  | ||||
|                             static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults())); | ||||
|  | @ -2130,59 +2382,45 @@ void Plater::priv::mirror(Axis axis) | |||
| 
 | ||||
| void Plater::priv::arrange() | ||||
| { | ||||
|     if (arranging) { return; } | ||||
|     arranging = true; | ||||
|     Slic3r::ScopeGuard arranging_guard([this]() { arranging = false; }); | ||||
|     m_ui_jobs.start(Jobs::Arrange); | ||||
| } | ||||
| 
 | ||||
|     wxBusyCursor wait; | ||||
| // This method will find an optimal orientation for the currently selected item
 | ||||
| // Very similar in nature to the arrange method above...
 | ||||
| void Plater::priv::sla_optimize_rotation() { | ||||
|     m_ui_jobs.start(Jobs::Rotoptimize); | ||||
| } | ||||
| 
 | ||||
|     this->background_process.stop(); | ||||
| void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { | ||||
|     // TODO: we should decide whether to allow arrange when the search is
 | ||||
|     // running we should probably disable explicit slicing and background
 | ||||
|     // processing
 | ||||
| 
 | ||||
|     unsigned count = 0; | ||||
|     for(auto obj : model.objects) count += obj->instances.size(); | ||||
|     static const auto arrangestr = _(L("Arranging")); | ||||
| 
 | ||||
|     auto prev_range = statusbar()->get_range(); | ||||
|     statusbar()->set_range(count); | ||||
| 
 | ||||
|     auto statusfn = [this, count] (unsigned st, const std::string& msg) { | ||||
|         /* // In case we would run the arrange asynchronously
 | ||||
|         wxCommandEvent event(EVT_PROGRESS_BAR); | ||||
|         event.SetInt(st); | ||||
|         event.SetString(msg); | ||||
|         wxQueueEvent(this->q, event.Clone()); */ | ||||
|         statusbar()->set_progress(count - st); | ||||
|         statusbar()->set_status_text(_(msg)); | ||||
| 
 | ||||
|         // ok, this is dangerous, but we are protected by the flag
 | ||||
|         // 'arranging' and the arrange button is also disabled.
 | ||||
|         // This call is needed for the cancel button to work.
 | ||||
|         wxYieldIfNeeded(); | ||||
|     }; | ||||
| 
 | ||||
|     statusbar()->set_cancel_callback([this, statusfn](){ | ||||
|         arranging = false; | ||||
|         statusfn(0, L("Arranging canceled")); | ||||
|     }); | ||||
| 
 | ||||
|     static const std::string arrangestr = L("Arranging"); | ||||
|     auto &config = plater().config; | ||||
|     auto &view3D = plater().view3D; | ||||
|     auto &model  = plater().model; | ||||
| 
 | ||||
|     // FIXME: I don't know how to obtain the minimum distance, it depends
 | ||||
|     // on printer technology. I guess the following should work but it crashes.
 | ||||
|     double dist = 6; //PrintConfig::min_object_distance(config);
 | ||||
|     if(printer_technology == ptFFF) { | ||||
|     double dist = 6; // PrintConfig::min_object_distance(config);
 | ||||
|     if (plater().printer_technology == ptFFF) { | ||||
|         dist = PrintConfig::min_object_distance(config); | ||||
|     } | ||||
| 
 | ||||
|     auto min_obj_distance = coord_t(dist/SCALING_FACTOR); | ||||
|     auto min_obj_distance = coord_t(dist / SCALING_FACTOR); | ||||
| 
 | ||||
|     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); | ||||
|     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>( | ||||
|         "bed_shape"); | ||||
| 
 | ||||
|     assert(bed_shape_opt); | ||||
|     auto& bedpoints = bed_shape_opt->values; | ||||
|     Polyline bed; bed.points.reserve(bedpoints.size()); | ||||
|     for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); | ||||
|     auto &   bedpoints = bed_shape_opt->values; | ||||
|     Polyline bed; | ||||
|     bed.points.reserve(bedpoints.size()); | ||||
|     for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); | ||||
| 
 | ||||
|     statusfn(0, arrangestr); | ||||
|     update_status(0, arrangestr); | ||||
| 
 | ||||
|     arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); | ||||
| 
 | ||||
|  | @ -2198,101 +2436,87 @@ void Plater::priv::arrange() | |||
|                      bed, | ||||
|                      hint, | ||||
|                      false, // create many piles not just one pile
 | ||||
|                      [statusfn](unsigned st) { statusfn(st, arrangestr); }, | ||||
|                      [this] () { return !arranging; }); | ||||
|     } catch(std::exception& /*e*/) { | ||||
|         GUI::show_error(this->q, L("Could not arrange model objects! " | ||||
|                                    "Some geometries may be invalid.")); | ||||
|                      [this](unsigned st) { | ||||
|                          if (st > 0) | ||||
|                              update_status(count - int(st), arrangestr); | ||||
|                      }, | ||||
|                      [this]() { return was_canceled(); }); | ||||
|     } catch (std::exception & /*e*/) { | ||||
|         GUI::show_error(plater().q, | ||||
|                         L("Could not arrange model objects! " | ||||
|                           "Some geometries may be invalid.")); | ||||
|     } | ||||
| 
 | ||||
|     update_status(count, | ||||
|                   was_canceled() ? _(L("Arranging canceled.")) | ||||
|                                  : _(L("Arranging done."))); | ||||
| 
 | ||||
|     // it remains to move the wipe tower:
 | ||||
|     view3D->get_canvas3d()->arrange_wipe_tower(wti); | ||||
| 
 | ||||
|     statusfn(0, L("Arranging done.")); | ||||
|     statusbar()->set_range(prev_range); | ||||
|     statusbar()->set_cancel_callback(); // remove cancel button
 | ||||
| 
 | ||||
|     // Do a full refresh of scene tree, including regenerating all the GLVolumes.
 | ||||
|     //FIXME The update function shall just reload the modified matrices.
 | ||||
|     update(true); | ||||
| } | ||||
| 
 | ||||
| // This method will find an optimal orientation for the currently selected item
 | ||||
| // Very similar in nature to the arrange method above...
 | ||||
| void Plater::priv::sla_optimize_rotation() { | ||||
| 
 | ||||
|     // TODO: we should decide whether to allow arrange when the search is
 | ||||
|     // running we should probably disable explicit slicing and background
 | ||||
|     // processing
 | ||||
| 
 | ||||
|     if (rotoptimizing) { return; } | ||||
|     rotoptimizing = true; | ||||
|     Slic3r::ScopeGuard rotoptimizing_guard([this]() { rotoptimizing = false; }); | ||||
| 
 | ||||
|     int obj_idx = get_selected_object_idx(); | ||||
| void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | ||||
| { | ||||
|     int obj_idx = plater().get_selected_object_idx(); | ||||
|     if (obj_idx < 0) { return; } | ||||
| 
 | ||||
|     ModelObject * o = model.objects[size_t(obj_idx)]; | ||||
| 
 | ||||
|     background_process.stop(); | ||||
| 
 | ||||
|     auto prev_range = statusbar()->get_range(); | ||||
|     statusbar()->set_range(100); | ||||
| 
 | ||||
|     auto stfn = [this] (unsigned st, const std::string& msg) { | ||||
|         statusbar()->set_progress(int(st)); | ||||
|         statusbar()->set_status_text(msg); | ||||
| 
 | ||||
|         // could be problematic, but we need the cancel button.
 | ||||
|         wxYieldIfNeeded(); | ||||
|     }; | ||||
| 
 | ||||
|     statusbar()->set_cancel_callback([this, stfn](){ | ||||
|         rotoptimizing = false; | ||||
|         stfn(0, L("Orientation search canceled")); | ||||
|     }); | ||||
|     ModelObject *o = plater().model.objects[size_t(obj_idx)]; | ||||
| 
 | ||||
|     auto r = sla::find_best_rotation( | ||||
|                 *o, .005f, | ||||
|                 [stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); }, | ||||
|                 [this](){ return !rotoptimizing; } | ||||
|     ); | ||||
|         *o, | ||||
|         .005f, | ||||
|         [this](unsigned s) { | ||||
|             if (s < 100) | ||||
|                 update_status(int(s), | ||||
|                               _(L("Searching for optimal orientation"))); | ||||
|         }, | ||||
|         [this]() { return was_canceled(); }); | ||||
| 
 | ||||
|     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); | ||||
|     const auto *bed_shape_opt = | ||||
|         plater().config->opt<ConfigOptionPoints>("bed_shape"); | ||||
|      | ||||
|     assert(bed_shape_opt); | ||||
| 
 | ||||
|     auto& bedpoints = bed_shape_opt->values; | ||||
|     Polyline bed; bed.points.reserve(bedpoints.size()); | ||||
|     for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); | ||||
|     auto &   bedpoints = bed_shape_opt->values; | ||||
|     Polyline bed; | ||||
|     bed.points.reserve(bedpoints.size()); | ||||
|     for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); | ||||
| 
 | ||||
|     double mindist = 6.0; // FIXME
 | ||||
|      | ||||
|     if(rotoptimizing) // wasn't canceled
 | ||||
|     for(ModelInstance * oi : o->instances) { | ||||
|         oi->set_rotation({r[X], r[Y], r[Z]}); | ||||
|          | ||||
|         Polygon trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); | ||||
|         MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); | ||||
|         double r = rotbb.angle_to_X(); | ||||
|          | ||||
|         // The box should be landscape
 | ||||
|         if(rotbb.width() < rotbb.height()) r += PI / 2; | ||||
|          | ||||
|         Vec3d rt = oi->get_rotation(); rt(Z) += r; | ||||
|         oi->set_rotation(rt); | ||||
|     if (!was_canceled()) { | ||||
|         for(ModelInstance * oi : o->instances) { | ||||
|             oi->set_rotation({r[X], r[Y], r[Z]}); | ||||
|      | ||||
|             auto    trmatrix = oi->get_transformation().get_matrix(); | ||||
|             Polygon trchull  = o->convex_hull_2d(trmatrix); | ||||
|              | ||||
|             MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); | ||||
|             double            r = rotbb.angle_to_X(); | ||||
|      | ||||
|             // The box should be landscape
 | ||||
|             if(rotbb.width() < rotbb.height()) r += PI / 2; | ||||
|              | ||||
|             Vec3d rt = oi->get_rotation(); rt(Z) += r; | ||||
|              | ||||
|             oi->set_rotation(rt); | ||||
|         } | ||||
|      | ||||
|         arr::WipeTowerInfo wti; // useless in SLA context
 | ||||
|         arr::find_new_position(plater().model, | ||||
|                                o->instances, | ||||
|                                coord_t(mindist / SCALING_FACTOR), | ||||
|                                bed, | ||||
|                                wti); | ||||
|      | ||||
|         // Correct the z offset of the object which was corrupted be
 | ||||
|         // the rotation
 | ||||
|         o->ensure_on_bed(); | ||||
|     } | ||||
| 
 | ||||
|     arr::WipeTowerInfo wti; // useless in SLA context
 | ||||
|     arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti); | ||||
| 
 | ||||
|     // Correct the z offset of the object which was corrupted be the rotation
 | ||||
|     o->ensure_on_bed(); | ||||
| 
 | ||||
|     stfn(0, L("Orientation found.")); | ||||
|     statusbar()->set_range(prev_range); | ||||
|     statusbar()->set_cancel_callback(); | ||||
| 
 | ||||
|     update(true); | ||||
|     update_status(100, | ||||
|                   was_canceled() ? _(L("Orientation search canceled.")) | ||||
|                                  : _(L("Orientation found."))); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::split_object() | ||||
|  | @ -2473,7 +2697,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation) | |||
| // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
 | ||||
| bool Plater::priv::restart_background_process(unsigned int state) | ||||
| { | ||||
|     if (arranging || rotoptimizing) { | ||||
|     if (m_ui_jobs.is_any_running()) { | ||||
|         // Avoid a race condition
 | ||||
|         return false; | ||||
|     } | ||||
|  | @ -2704,7 +2928,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) | |||
| void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) | ||||
| { | ||||
|     if (evt.status.percent >= -1) { | ||||
|         if (arranging || rotoptimizing) { | ||||
|         if (m_ui_jobs.is_any_running()) { | ||||
|             // Avoid a race condition
 | ||||
|             return; | ||||
|         } | ||||
|  | @ -3185,7 +3409,7 @@ bool Plater::priv::can_fix_through_netfabb() const | |||
| 
 | ||||
| bool Plater::priv::can_increase_instances() const | ||||
| { | ||||
|     if (arranging || rotoptimizing) { | ||||
|     if (m_ui_jobs.is_any_running()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|  | @ -3195,7 +3419,7 @@ bool Plater::priv::can_increase_instances() const | |||
| 
 | ||||
| bool Plater::priv::can_decrease_instances() const | ||||
| { | ||||
|     if (arranging || rotoptimizing) { | ||||
|     if (m_ui_jobs.is_any_running()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|  | @ -3215,7 +3439,7 @@ bool Plater::priv::can_split_to_volumes() const | |||
| 
 | ||||
| bool Plater::priv::can_arrange() const | ||||
| { | ||||
|     return !model.objects.empty() && !arranging; | ||||
|     return !model.objects.empty() && !m_ui_jobs.is_any_running(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_layers_editing() const | ||||
|  | @ -3282,6 +3506,7 @@ SLAPrint&       Plater::sla_print()         { return p->sla_print; } | |||
| 
 | ||||
| void Plater::new_project() | ||||
| { | ||||
|     p->select_view_3D("3D"); | ||||
|     wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); | ||||
| } | ||||
| 
 | ||||
|  | @ -3342,6 +3567,8 @@ void Plater::load_files(const std::vector<std::string>& input_files, bool load_m | |||
| 
 | ||||
| void Plater::update() { p->update(); } | ||||
| 
 | ||||
| void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); } | ||||
| 
 | ||||
| void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } | ||||
| 
 | ||||
| void Plater::select_view(const std::string& direction) { p->select_view(direction); } | ||||
|  | @ -3542,7 +3769,7 @@ void Plater::export_stl(bool extended, bool selection_only) | |||
|         else | ||||
|         { | ||||
|             const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|             mesh = model_object->volumes[volume->volume_idx()]->mesh; | ||||
|             mesh = model_object->volumes[volume->volume_idx()]->mesh(); | ||||
|             mesh.transform(volume->get_volume_transformation().get_matrix()); | ||||
|             mesh.translate(-model_object->origin_translation.cast<float>()); | ||||
|         } | ||||
|  | @ -3648,7 +3875,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) | |||
|     if (!path.Lower().EndsWith(".3mf")) | ||||
|         return; | ||||
| 
 | ||||
| 	DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); | ||||
|     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); | ||||
|     const std::string path_u8 = into_u8(path); | ||||
|     wxBusyCursor wait; | ||||
|     if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { | ||||
|  | @ -3664,6 +3891,9 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) | |||
| 
 | ||||
| void Plater::reslice() | ||||
| { | ||||
|     // Stop arrange and (or) optimize rotation tasks.
 | ||||
|     this->stop_jobs(); | ||||
|      | ||||
|     //FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
 | ||||
|     // bitmask of UpdateBackgroundProcessReturnState
 | ||||
|     unsigned int state = this->p->update_background_process(true); | ||||
|  | @ -3699,7 +3929,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object) | |||
|     if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) | ||||
|         this->p->view3D->reload_scene(false); | ||||
| 
 | ||||
| 	if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) | ||||
|     if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) | ||||
|         // Nothing to do on empty input or invalid configuration.
 | ||||
|         return; | ||||
| 
 | ||||
|  |  | |||
|  | @ -144,6 +144,7 @@ public: | |||
|     void load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true); | ||||
| 
 | ||||
|     void update(); | ||||
|     void stop_jobs(); | ||||
|     void select_view(const std::string& direction); | ||||
|     void select_view_3D(const std::string& name); | ||||
| 
 | ||||
|  |  | |||
|  | @ -509,6 +509,7 @@ const std::vector<std::string>& Preset::sla_printer_options() | |||
|             "printer_technology", | ||||
|             "bed_shape", "max_print_height", | ||||
|             "display_width", "display_height", "display_pixels_x", "display_pixels_y", | ||||
|             "display_mirror_x", "display_mirror_y", | ||||
|             "display_orientation", | ||||
|             "fast_tilt_time", "slow_tilt_time", "area_fill", | ||||
|             "relative_correction", | ||||
|  |  | |||
|  | @ -781,7 +781,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool | |||
|                     if (i == 0) | ||||
|                         suffix[0] = 0; | ||||
|                     else | ||||
|                         sprintf(suffix, "%d", i); | ||||
|                         sprintf(suffix, "%d", (int)i); | ||||
|                     std::string new_name = name + suffix; | ||||
|                     loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), | ||||
|                         new_name, std::move(cfg), i == 0); | ||||
|  | @ -837,7 +837,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const | |||
|                     return preset_name_dst; | ||||
|                 // Try to generate another name.
 | ||||
|                 char buf[64]; | ||||
|                 sprintf(buf, " (%d)", i); | ||||
|                 sprintf(buf, " (%d)", (int)i); | ||||
|                 preset_name_dst = preset_name_src + buf + bundle_name; | ||||
|             } | ||||
|         } | ||||
|  | @ -1379,7 +1379,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst | |||
|     for (size_t i = 0; i < this->filament_presets.size(); ++ i) { | ||||
|         char suffix[64]; | ||||
|         if (i > 0) | ||||
|             sprintf(suffix, "_%d", i); | ||||
|             sprintf(suffix, "_%d", (int)i); | ||||
|         else | ||||
|             suffix[0] = 0; | ||||
|         c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; | ||||
|  |  | |||
|  | @ -168,6 +168,11 @@ void ProgressStatusBar::set_status_text(const char *txt) | |||
|     this->set_status_text(wxString::FromUTF8(txt)); | ||||
| } | ||||
| 
 | ||||
| wxString ProgressStatusBar::get_status_text() const | ||||
| { | ||||
|     return self->GetStatusText(); | ||||
| } | ||||
| 
 | ||||
| void ProgressStatusBar::show_cancel_button() | ||||
| { | ||||
|     if(m_cancelbutton) m_cancelbutton->Show(); | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| 
 | ||||
| #include <memory> | ||||
| #include <functional> | ||||
| #include <string> | ||||
| 
 | ||||
| class wxTimer; | ||||
| class wxGauge; | ||||
|  | @ -51,6 +52,7 @@ public: | |||
|     void        set_status_text(const wxString& txt); | ||||
|     void        set_status_text(const std::string& txt); | ||||
|     void        set_status_text(const char *txt); | ||||
|     wxString    get_status_text() const; | ||||
| 
 | ||||
|     // Temporary methods to satisfy Perl side
 | ||||
|     void        show_cancel_button(); | ||||
|  |  | |||
|  | @ -296,6 +296,9 @@ void Selection::clear() | |||
|     if (!m_valid) | ||||
|         return; | ||||
| 
 | ||||
|     if (m_list.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     for (unsigned int i : m_list) | ||||
|     { | ||||
|         (*m_volumes)[i]->selected = false; | ||||
|  |  | |||
|  | @ -333,6 +333,8 @@ private: | |||
|     void render_sidebar_rotation_hint(Axis axis) const; | ||||
|     void render_sidebar_scale_hint(Axis axis) const; | ||||
|     void render_sidebar_size_hint(Axis axis, double length) const; | ||||
| 
 | ||||
| public: | ||||
|     enum SyncRotationType { | ||||
|         // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis.
 | ||||
|         SYNC_ROTATION_NONE = 0, | ||||
|  | @ -343,6 +345,8 @@ private: | |||
|     }; | ||||
|     void synchronize_unselected_instances(SyncRotationType sync_rotation_type); | ||||
|     void synchronize_unselected_volumes(); | ||||
| 
 | ||||
| private: | ||||
|     void ensure_on_bed(); | ||||
|     bool is_from_fully_selected_instance(unsigned int volume_idx) const; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1854,13 +1854,17 @@ void TabPrinter::build_fff() | |||
| 
 | ||||
| 			btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) | ||||
| 			{ | ||||
| 				auto dlg = new BedShapeDialog(this); | ||||
| 				dlg->build_dialog(m_config->option<ConfigOptionPoints>("bed_shape")); | ||||
| 				if (dlg->ShowModal() == wxID_OK) { | ||||
| 					load_key_value("bed_shape", dlg->GetValue()); | ||||
| 					update_changed_ui(); | ||||
| 				} | ||||
| 			})); | ||||
|                 BedShapeDialog dlg(this); | ||||
|                 dlg.build_dialog(m_config->option<ConfigOptionPoints>("bed_shape")); | ||||
|                 if (dlg.ShowModal() == wxID_OK) { | ||||
|                     std::vector<Vec2d> shape = dlg.GetValue(); | ||||
|                     if (!shape.empty()) | ||||
|                     { | ||||
|                         load_key_value("bed_shape", shape); | ||||
|                         update_changed_ui(); | ||||
|                     } | ||||
|                 } | ||||
|             })); | ||||
| 
 | ||||
| 			return sizer; | ||||
| 		}; | ||||
|  | @ -2056,11 +2060,15 @@ void TabPrinter::build_sla() | |||
| 
 | ||||
|         btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) | ||||
|         { | ||||
|             auto dlg = new BedShapeDialog(this); | ||||
|             dlg->build_dialog(m_config->option<ConfigOptionPoints>("bed_shape")); | ||||
|             if (dlg->ShowModal() == wxID_OK) { | ||||
|                 load_key_value("bed_shape", dlg->GetValue()); | ||||
|                 update_changed_ui(); | ||||
|             BedShapeDialog dlg(this); | ||||
|             dlg.build_dialog(m_config->option<ConfigOptionPoints>("bed_shape")); | ||||
|             if (dlg.ShowModal() == wxID_OK) { | ||||
|                 std::vector<Vec2d> shape = dlg.GetValue(); | ||||
|                 if (!shape.empty()) | ||||
|                 { | ||||
|                     load_key_value("bed_shape", shape); | ||||
|                     update_changed_ui(); | ||||
|                 } | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|  | @ -2079,6 +2087,10 @@ void TabPrinter::build_sla() | |||
|     line.append_option(optgroup->get_option("display_pixels_y")); | ||||
|     optgroup->append_line(line); | ||||
|     optgroup->append_single_option_line("display_orientation"); | ||||
|      | ||||
|     // FIXME: This should be on one line in the UI
 | ||||
|     optgroup->append_single_option_line("display_mirror_x"); | ||||
|     optgroup->append_single_option_line("display_mirror_y"); | ||||
| 
 | ||||
|     optgroup = page->new_optgroup(_(L("Tilt"))); | ||||
|     line = { _(L("Tilt time")), "" }; | ||||
|  |  | |||
|  | @ -586,7 +586,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent | |||
| 		ItemAdded(parent_item, child); | ||||
| 
 | ||||
|         root->m_volumes_cnt++; | ||||
|         if (insert_position > 0) insert_position++; | ||||
|         if (insert_position >= 0) insert_position++; | ||||
| 	} | ||||
| 
 | ||||
|     const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); | ||||
|  |  | |||
|  | @ -389,10 +389,10 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) | |||
| 	 				throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); | ||||
| 				if (model.objects.front()->volumes.size() > 1) | ||||
| 	 				throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); | ||||
| 	 			meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh)); | ||||
| 	 			meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); | ||||
| 			} | ||||
| 			for (size_t i = 0; i < volumes.size(); ++ i) { | ||||
| 				volumes[i]->mesh = std::move(meshes_repaired[i]); | ||||
| 				volumes[i]->set_mesh(std::move(meshes_repaired[i])); | ||||
| 				volumes[i]->set_new_unique_id(); | ||||
| 			} | ||||
| 			model_object.invalidate_bounding_box(); | ||||
|  |  | |||
|  | @ -384,7 +384,13 @@ void Serial::reset_line_num() | |||
| 
 | ||||
| bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) | ||||
| { | ||||
| 	auto &io_service = get_io_service(); | ||||
| 	auto& io_service = | ||||
| #if BOOST_VERSION >= 107000 | ||||
| 		//FIXME this is most certainly wrong!
 | ||||
| 		(boost::asio::io_context&)this->get_executor().context(); | ||||
|  #else | ||||
| 		this->get_io_service(); | ||||
| #endif | ||||
| 	asio::deadline_timer timer(io_service); | ||||
| 	char c = 0; | ||||
| 	bool fail = false; | ||||
|  |  | |||
|  | @ -119,8 +119,6 @@ target_include_directories(XS PRIVATE src ${LIBDIR}/libslic3r) | |||
| target_compile_definitions(XS PRIVATE -DSLIC3RXS) | ||||
| set_target_properties(XS PROPERTIES PREFIX "") # Prevent cmake from generating libXS.so instead of XS.so | ||||
| 
 | ||||
| target_link_libraries(XS ${Boost_LIBRARIES}) | ||||
| 
 | ||||
| if (APPLE) | ||||
|     # -liconv: boost links to libiconv by default | ||||
|     target_link_libraries(XS "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) | ||||
|  | @ -156,12 +154,6 @@ if (WIN32) | |||
|     target_link_libraries(XS ${PERL_LIBRARY}) | ||||
| endif() | ||||
| 
 | ||||
| target_link_libraries(XS ${Boost_LIBRARIES}) | ||||
| target_link_libraries(XS ${TBB_LIBRARIES}) | ||||
| # target_link_libraries(XS ${wxWidgets_LIBRARIES}) | ||||
| target_link_libraries(XS ${EXPAT_LIBRARIES}) | ||||
| # target_link_libraries(XS ${GLEW_LIBRARIES}) | ||||
| 
 | ||||
| # Install the XS.pm and XS.{so,dll,bundle} into the local-lib directory. | ||||
| set(PERL_LOCAL_LIB_DIR "../../local-lib/lib/perl5/${PerlEmbed_ARCHNAME}") | ||||
| add_custom_command( | ||||
|  | @ -181,10 +173,6 @@ if(APPLE) | |||
|     ) | ||||
| endif() | ||||
| 
 | ||||
| if(SLIC3R_PROFILE) | ||||
|     target_link_libraries(Shiny) | ||||
| endif() | ||||
| 
 | ||||
| if (MSVC) | ||||
|     # Here we associate some additional properties with the MSVC project to enable compilation and debugging out of the box. | ||||
|     get_filename_component(PROPS_PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) | ||||
|  |  | |||
|  | @ -69,7 +69,10 @@ extern "C" { | |||
|     #undef fwrite | ||||
|     #undef fclose | ||||
|     #undef sleep | ||||
|     #undef snprintf | ||||
|     #undef strerror | ||||
|     #undef test | ||||
|     #undef times | ||||
|     #undef accept | ||||
|     #undef wait | ||||
| 
 | ||||
|  |  | |||
|  | @ -253,7 +253,7 @@ ModelMaterial::attributes() | |||
|     Ref<DynamicPrintConfig> config() | ||||
|         %code%{ RETVAL = &THIS->config; %}; | ||||
|     Ref<TriangleMesh> mesh() | ||||
|         %code%{ RETVAL = &THIS->mesh; %}; | ||||
|         %code%{ RETVAL = &THIS->mesh(); %}; | ||||
|      | ||||
|     bool modifier() | ||||
|         %code%{ RETVAL = THIS->is_modifier(); %}; | ||||
|  |  | |||
|  | @ -46,7 +46,6 @@ TriangleMesh::ReadFromPerl(vertices, facets) | |||
|     SV* facets | ||||
|     CODE: | ||||
|         stl_file &stl = THIS->stl; | ||||
|         stl.error = 0; | ||||
|         stl.stats.type = inmemory; | ||||
|      | ||||
|         // count facets and allocate memory | ||||
|  | @ -99,20 +98,18 @@ SV* | |||
| TriangleMesh::vertices() | ||||
|     CODE: | ||||
|         if (!THIS->repaired) CONFESS("vertices() requires repair()"); | ||||
|          | ||||
|         if (THIS->stl.v_shared == NULL) | ||||
|             stl_generate_shared_vertices(&(THIS->stl)); | ||||
|         THIS->require_shared_vertices(); | ||||
|          | ||||
|         // vertices | ||||
|         AV* vertices = newAV(); | ||||
|         av_extend(vertices, THIS->stl.stats.shared_vertices); | ||||
|         for (int i = 0; i < THIS->stl.stats.shared_vertices; i++) { | ||||
|         av_extend(vertices, THIS->its.vertices.size()); | ||||
|         for (size_t i = 0; i < THIS->its.vertices.size(); i++) { | ||||
|             AV* vertex = newAV(); | ||||
|             av_store(vertices, i, newRV_noinc((SV*)vertex)); | ||||
|             av_extend(vertex, 2); | ||||
|             av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i](0))); | ||||
|             av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i](1))); | ||||
|             av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i](2))); | ||||
|             av_store(vertex, 0, newSVnv(THIS->its.vertices[i](0))); | ||||
|             av_store(vertex, 1, newSVnv(THIS->its.vertices[i](1))); | ||||
|             av_store(vertex, 2, newSVnv(THIS->its.vertices[i](2))); | ||||
|         } | ||||
|          | ||||
|         RETVAL = newRV_noinc((SV*)vertices); | ||||
|  | @ -123,9 +120,7 @@ SV* | |||
| TriangleMesh::facets() | ||||
|     CODE: | ||||
|         if (!THIS->repaired) CONFESS("facets() requires repair()"); | ||||
|          | ||||
|         if (THIS->stl.v_shared == NULL) | ||||
|             stl_generate_shared_vertices(&(THIS->stl)); | ||||
|         THIS->require_shared_vertices(); | ||||
|          | ||||
|         // facets | ||||
|         AV* facets = newAV(); | ||||
|  | @ -134,9 +129,9 @@ TriangleMesh::facets() | |||
|             AV* facet = newAV(); | ||||
|             av_store(facets, i, newRV_noinc((SV*)facet)); | ||||
|             av_extend(facet, 2); | ||||
|             av_store(facet, 0, newSVnv(THIS->stl.v_indices[i].vertex[0])); | ||||
|             av_store(facet, 1, newSVnv(THIS->stl.v_indices[i].vertex[1])); | ||||
|             av_store(facet, 2, newSVnv(THIS->stl.v_indices[i].vertex[2])); | ||||
|             av_store(facet, 0, newSVnv(THIS->its.indices[i][0])); | ||||
|             av_store(facet, 1, newSVnv(THIS->its.indices[i][1])); | ||||
|             av_store(facet, 2, newSVnv(THIS->its.indices[i][2])); | ||||
|         } | ||||
|          | ||||
|         RETVAL = newRV_noinc((SV*)facets); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros