mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into ys_bugfixing
This commit is contained in:
		
						commit
						09f4831f4e
					
				
					 86 changed files with 4705 additions and 4380 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										
											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 | 
|  | @ -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> | ||||
|  |  | |||
										
											
												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); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES | |||
|     ${SRC_DIR}/libnest2d/optimizer.hpp | ||||
|     ${SRC_DIR}/libnest2d/utils/metaloop.hpp | ||||
|     ${SRC_DIR}/libnest2d/utils/rotfinder.hpp | ||||
|     ${SRC_DIR}/libnest2d/utils/rotcalipers.hpp | ||||
|     ${SRC_DIR}/libnest2d/utils/bigint.hpp | ||||
|     ${SRC_DIR}/libnest2d/utils/rational.hpp | ||||
|     ${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp | ||||
|     ${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp | ||||
|     ${SRC_DIR}/libnest2d/placers/nfpplacer.hpp | ||||
|  | @ -70,12 +73,13 @@ if(TBB_FOUND) | |||
|     # The Intel TBB library will use the std::exception_ptr feature of C++11. | ||||
|     target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) | ||||
| 
 | ||||
|     target_link_libraries(libnest2d INTERFACE tbb) | ||||
|     # The following breaks compilation on Visual Studio in Debug mode. | ||||
|     #find_package(Threads REQUIRED) | ||||
|     #target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} | ||||
|     #    Threads::Threads | ||||
|     #    ) | ||||
|     find_package(Threads REQUIRED) | ||||
|     target_link_libraries(libnest2d INTERFACE  | ||||
|         tbb # VS debug mode needs linking this way: | ||||
|         # ${TBB_LIBRARIES}  | ||||
|         ${CMAKE_DL_LIBS} | ||||
|         Threads::Threads | ||||
|         ) | ||||
| else() | ||||
|    find_package(OpenMP QUIET) | ||||
| 
 | ||||
|  | @ -92,10 +96,11 @@ endif() | |||
| 
 | ||||
| add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) | ||||
| target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend) | ||||
| 
 | ||||
| add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) | ||||
| target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer) | ||||
| 
 | ||||
| #target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) | ||||
| # target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) | ||||
| target_include_directories(libnest2d INTERFACE ${SRC_DIR}) | ||||
| 
 | ||||
| if(NOT LIBNEST2D_HEADER_ONLY) | ||||
|  |  | |||
|  | @ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer<Box>; | |||
| // This supports only box shaped bins
 | ||||
| using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>; | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
| 
 | ||||
| extern template class Nester<NfpPlacer, FirstFitSelection>; | ||||
| extern template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||
| extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute( | ||||
|         std::vector<Item>::iterator, std::vector<Item>::iterator); | ||||
| extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute( | ||||
|         std::vector<Item>::iterator, std::vector<Item>::iterator); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Iterator = std::vector<Item>::iterator> | ||||
|  | @ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to, | |||
|     return nester.execute(from, to); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
| PackGroup nest(Container&& cont, | ||||
|                const typename Placer::BinType& bin, | ||||
|                Coord dist = 0, | ||||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), | ||||
|                                   bin, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Iterator = std::vector<Item>::iterator> | ||||
|  | @ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to, | |||
|     return nester.execute(from, to); | ||||
| } | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
| 
 | ||||
| extern template class Nester<NfpPlacer, FirstFitSelection>; | ||||
| extern template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||
| 
 | ||||
| extern template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                                std::vector<Item>::iterator to, | ||||
|                                const Box& bin, | ||||
|                                Coord dist = 0, | ||||
|                                const NfpPlacer::Config& pconf, | ||||
|                                const FirstFitSelection::Config& sconf); | ||||
| 
 | ||||
| extern template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                                std::vector<Item>::iterator to, | ||||
|                                const Box& bin, | ||||
|                                ProgressFunction prg, | ||||
|                                StopCondition scond, | ||||
|                                Coord dist = 0, | ||||
|                                const NfpPlacer::Config& pconf, | ||||
|                                const FirstFitSelection::Config& sconf); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
| PackGroup nest(Container&& cont, | ||||
|                const typename Placer::BinType& bin, | ||||
|                Coord dist = 0, | ||||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), | ||||
|                                   bin, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
|  | @ -105,71 +139,6 @@ PackGroup nest(Container&& cont, | |||
|                                   bin, prg, scond, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>( | ||||
|     std::vector<Item>& cont, | ||||
|     const Box& bin, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>( | ||||
|     std::vector<Item>& cont, | ||||
|     const Box& bin, | ||||
|     ProgressFunction prg, | ||||
|     StopCondition scond, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>( | ||||
|     std::vector<Item>&& cont, | ||||
|     const Box& bin, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>( | ||||
|     std::vector<Item>&& cont, | ||||
|     const Box& bin, | ||||
|     ProgressFunction prg, | ||||
|     StopCondition scond, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>( | ||||
|     std::vector<Item>::iterator from, | ||||
|     std::vector<Item>::iterator to, | ||||
|     const Box& bin, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>( | ||||
|     std::vector<Item>::iterator from, | ||||
|     std::vector<Item>::iterator to, | ||||
|     const Box& bin, | ||||
|     ProgressFunction prg, | ||||
|     StopCondition scond, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // LIBNEST2D_H
 | ||||
|  |  | |||
|  | @ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a | |||
|             #                  ${clipper_library_BINARY_DIR} | ||||
|             # ) | ||||
| 
 | ||||
|             add_library(ClipperBackend STATIC | ||||
|             add_library(clipperBackend STATIC | ||||
|                 ${clipper_library_SOURCE_DIR}/clipper.cpp | ||||
|                 ${clipper_library_SOURCE_DIR}/clipper.hpp) | ||||
| 
 | ||||
|             target_include_directories(ClipperBackend INTERFACE  | ||||
|                 ${clipper_library_SOURCE_DIR}) | ||||
|             target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR}) | ||||
|         else() | ||||
|             message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. | ||||
|                 You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") | ||||
|         endif() | ||||
|     else() | ||||
|         add_library(ClipperBackend INTERFACE) | ||||
|         target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper) | ||||
|         add_library(clipperBackend INTERFACE) | ||||
|         target_link_libraries(clipperBackend INTERFACE Clipper::Clipper) | ||||
|     endif() | ||||
| else() | ||||
|     # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) | ||||
|  | @ -69,6 +68,6 @@ target_link_libraries(clipperBackend INTERFACE Boost::boost ) | |||
| 
 | ||||
| target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) | ||||
| 
 | ||||
| # And finally plug the ClipperBackend into libnest2d | ||||
| #target_link_libraries(libnest2d INTERFACE ClipperBackend) | ||||
| # And finally plug the clipperBackend into libnest2d | ||||
| # target_link_libraries(libnest2d INTERFACE clipperBackend) | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,13 +12,13 @@ struct Polygon { | |||
|     inline Polygon() = default; | ||||
| 
 | ||||
|     inline explicit Polygon(const Path& cont): Contour(cont) {} | ||||
|     inline explicit Polygon(const Paths& holes): | ||||
|         Holes(holes) {} | ||||
| //    inline explicit Polygon(const Paths& holes):
 | ||||
| //        Holes(holes) {}
 | ||||
|     inline Polygon(const Path& cont, const Paths& holes): | ||||
|         Contour(cont), Holes(holes) {} | ||||
| 
 | ||||
|     inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} | ||||
|     inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} | ||||
| //    inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
 | ||||
|     inline Polygon(Path&& cont, Paths&& holes): | ||||
|         Contour(std::move(cont)), Holes(std::move(holes)) {} | ||||
| }; | ||||
|  | @ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { | |||
|     return p; | ||||
| } | ||||
| 
 | ||||
| inline IntPoint operator -(IntPoint& p ) { | ||||
| inline IntPoint operator -(const IntPoint& p ) { | ||||
|     IntPoint ret = p; | ||||
|     ret.X = -ret.X; | ||||
|     ret.Y = -ret.Y; | ||||
|  |  | |||
|  | @ -20,43 +20,23 @@ using PathImpl  = ClipperLib::Path; | |||
| using HoleStore = ClipperLib::Paths; | ||||
| using PolygonImpl = ClipperLib::Polygon; | ||||
| 
 | ||||
| // Type of coordinate units used by Clipper
 | ||||
| template<> struct CoordType<PointImpl> { | ||||
|     using Type = ClipperLib::cInt; | ||||
| }; | ||||
| 
 | ||||
| // Type of point used by Clipper
 | ||||
| template<> struct PointType<PolygonImpl> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct PointType<PathImpl> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct PointType<PointImpl> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct CountourType<PolygonImpl> { | ||||
|     using Type = PathImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; }; | ||||
| template<> struct ShapeTag<PathImpl> { using Type = PathTag; }; | ||||
| template<> struct ShapeTag<PointImpl> { using Type = PointTag; }; | ||||
| template<> struct ShapeTag<PathImpl>    { using Type = PathTag; }; | ||||
| template<> struct ShapeTag<PointImpl>   { using Type = PointTag; }; | ||||
| 
 | ||||
| template<> struct ShapeTag<TMultiShape<PolygonImpl>> { | ||||
|     using Type = MultiPolygonTag; | ||||
| }; | ||||
| // Type of coordinate units used by Clipper. Enough to specialize for point,
 | ||||
| // the rest of the types will work (Path, Polygon)
 | ||||
| template<> struct CoordType<PointImpl> { using Type = ClipperLib::cInt; }; | ||||
| 
 | ||||
| template<> struct PointType<TMultiShape<PolygonImpl>> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| // Enough to specialize for path, it will work for multishape and Polygon
 | ||||
| template<> struct PointType<PathImpl> { using Type = PointImpl; }; | ||||
| 
 | ||||
| template<> struct HolesContainer<PolygonImpl> { | ||||
|     using Type = ClipperLib::Paths; | ||||
| }; | ||||
| // This is crucial. CountourType refers to itself by default, so we don't have
 | ||||
| // to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
 | ||||
| template<> struct ContourType<PolygonImpl> { using Type = PathImpl; }; | ||||
| 
 | ||||
| // The holes are contained in Clipper::Paths
 | ||||
| template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; }; | ||||
| 
 | ||||
| namespace pointlike { | ||||
| 
 | ||||
|  | @ -86,39 +66,11 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p) | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Using the libnest2d default area implementation
 | ||||
| #define DISABLE_BOOST_AREA | ||||
| 
 | ||||
| namespace _smartarea { | ||||
| 
 | ||||
| template<Orientation o> | ||||
| inline double area(const PolygonImpl& /*sh*/) { | ||||
|     return std::nan(""); | ||||
| } | ||||
| 
 | ||||
| template<> | ||||
| inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) { | ||||
|     return std::accumulate(sh.Holes.begin(), sh.Holes.end(), | ||||
|                            ClipperLib::Area(sh.Contour), | ||||
|                            [](double a, const ClipperLib::Path& pt){ | ||||
|         return a + ClipperLib::Area(pt); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<> | ||||
| inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) { | ||||
|     return -area<Orientation::COUNTER_CLOCKWISE>(sh); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| namespace shapelike { | ||||
| 
 | ||||
| // Tell libnest2d how to make string out of a ClipperPolygon object
 | ||||
| template<> inline double area(const PolygonImpl& sh, const PolygonTag&) | ||||
| { | ||||
|     return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh); | ||||
| } | ||||
| 
 | ||||
| template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance) | ||||
| { | ||||
|     #define DISABLE_BOOST_OFFSET | ||||
|  | @ -200,43 +152,16 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) | |||
| { | ||||
|     PolygonImpl p; | ||||
|     p.Contour = path; | ||||
| 
 | ||||
|     // Expecting that the coordinate system Y axis is positive in upwards
 | ||||
|     // direction
 | ||||
|     if(ClipperLib::Orientation(p.Contour)) { | ||||
|         // Not clockwise then reverse the b*tch
 | ||||
|         ClipperLib::ReversePath(p.Contour); | ||||
|     } | ||||
| 
 | ||||
|     p.Holes = holes; | ||||
|     for(auto& h : p.Holes) { | ||||
|         if(!ClipperLib::Orientation(h)) { | ||||
|             ClipperLib::ReversePath(h); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     | ||||
|     return p; | ||||
| } | ||||
| 
 | ||||
| template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { | ||||
|     PolygonImpl p; | ||||
|     p.Contour.swap(path); | ||||
| 
 | ||||
|     // Expecting that the coordinate system Y axis is positive in upwards
 | ||||
|     // direction
 | ||||
|     if(ClipperLib::Orientation(p.Contour)) { | ||||
|         // Not clockwise then reverse the b*tch
 | ||||
|         ClipperLib::ReversePath(p.Contour); | ||||
|     } | ||||
| 
 | ||||
|     p.Holes.swap(holes); | ||||
| 
 | ||||
|     for(auto& h : p.Holes) { | ||||
|         if(!ClipperLib::Orientation(h)) { | ||||
|             ClipperLib::ReversePath(h); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     return p; | ||||
| } | ||||
| 
 | ||||
|  | @ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) | |||
| } // namespace shapelike
 | ||||
| 
 | ||||
| #define DISABLE_BOOST_NFP_MERGE | ||||
| inline std::vector<PolygonImpl> clipper_execute( | ||||
| inline TMultiShape<PolygonImpl> clipper_execute( | ||||
|         ClipperLib::Clipper& clipper, | ||||
|         ClipperLib::ClipType clipType, | ||||
|         ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, | ||||
|         ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) | ||||
| { | ||||
|     shapelike::Shapes<PolygonImpl> retv; | ||||
|     TMultiShape<PolygonImpl> retv; | ||||
| 
 | ||||
|     ClipperLib::PolyTree result; | ||||
|     clipper.Execute(clipType, result, subjFillType, clipFillType); | ||||
|  | @ -370,8 +295,8 @@ inline std::vector<PolygonImpl> clipper_execute( | |||
| 
 | ||||
| namespace nfp { | ||||
| 
 | ||||
| template<> inline std::vector<PolygonImpl> | ||||
| merge(const std::vector<PolygonImpl>& shapes) | ||||
| template<> inline TMultiShape<PolygonImpl> | ||||
| merge(const TMultiShape<PolygonImpl>& shapes) | ||||
| { | ||||
|     ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); | ||||
| 
 | ||||
|  | @ -394,6 +319,8 @@ merge(const std::vector<PolygonImpl>& shapes) | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| #define DISABLE_BOOST_CONVEX_HULL | ||||
| 
 | ||||
| //#define DISABLE_BOOST_SERIALIZE
 | ||||
| //#define DISABLE_BOOST_UNSERIALIZE
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <string> | ||||
| #include <cmath> | ||||
| #include <type_traits> | ||||
| #include <limits> | ||||
| 
 | ||||
| #if defined(_MSC_VER) &&  _MSC_VER <= 1800 || __cplusplus < 201103L | ||||
|     #define BP2D_NOEXCEPT | ||||
|  | @ -197,6 +198,33 @@ public: | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct ScalarTag {}; | ||||
| struct BigIntTag {}; | ||||
| struct RationalTag {}; | ||||
| 
 | ||||
| template<class T> struct _NumTag {  | ||||
|     using Type =  | ||||
|         enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;  | ||||
| }; | ||||
| 
 | ||||
| template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type; | ||||
| 
 | ||||
| /// A local version for abs that is garanteed to work with libnest2d types
 | ||||
| template <class T> inline T abs(const T& v, ScalarTag)  | ||||
| {  | ||||
|     return std::abs(v);  | ||||
| } | ||||
| 
 | ||||
| template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); } | ||||
| 
 | ||||
| template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)  | ||||
| { | ||||
|     return static_cast<T2>(v);     | ||||
| } | ||||
| 
 | ||||
| template<class T2, class T1> inline T2 cast(const T1& v) {  | ||||
|     return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>()); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| #endif // LIBNEST2D_CONFIG_HPP
 | ||||
|  |  | |||
|  | @ -7,45 +7,125 @@ | |||
| #include <array> | ||||
| #include <vector> | ||||
| #include <numeric> | ||||
| #include <limits> | ||||
| #include <iterator> | ||||
| #include <cmath> | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #include "common.hpp" | ||||
| #include <libnest2d/common.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| // Meta tags for different geometry concepts. 
 | ||||
| struct PointTag {}; | ||||
| struct PolygonTag {}; | ||||
| struct PathTag {}; | ||||
| struct MultiPolygonTag {}; | ||||
| struct BoxTag {}; | ||||
| struct CircleTag {}; | ||||
| 
 | ||||
| /// Meta-function to derive the tag of a shape type.
 | ||||
| template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; }; | ||||
| 
 | ||||
| /// Tag<S> will be used instead of `typename ShapeTag<S>::Type`
 | ||||
| template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| /// Meta function to derive the contour type for a polygon which could be itself
 | ||||
| template<class RawShape> struct ContourType { using Type = RawShape; }; | ||||
| 
 | ||||
| /// TContour<RawShape> instead of `typename ContourType<RawShape>::type`
 | ||||
| template<class RawShape> | ||||
| using TContour = typename ContourType<remove_cvref_t<RawShape>>::Type; | ||||
| 
 | ||||
| /// Getting the type of point structure used by a shape.
 | ||||
| template<class Sh> struct PointType {  | ||||
|     using Type = typename PointType<TContour<Sh>>::Type;  | ||||
| }; | ||||
| 
 | ||||
| /// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
 | ||||
| template<class Shape> | ||||
| using TPoint = typename PointType<remove_cvref_t<Shape>>::Type; | ||||
| 
 | ||||
| /// Getting the coordinate data type for a geometry class.
 | ||||
| template<class GeomClass> struct CoordType { using Type = long; }; | ||||
| template<class GeomClass> struct CoordType {  | ||||
|     using Type = typename CoordType<TPoint<GeomClass>>::Type;  | ||||
| }; | ||||
| 
 | ||||
| /// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
 | ||||
| template<class GeomType> | ||||
| using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type; | ||||
| 
 | ||||
| 
 | ||||
| /// Getting the type of point structure used by a shape.
 | ||||
| template<class Sh> struct PointType { using Type = typename Sh::PointType; }; | ||||
| /// Getting the computation type for a certain geometry type.
 | ||||
| /// It is the coordinate type by default but it is advised that a type with
 | ||||
| /// larger precision and (or) range is specified.
 | ||||
| template<class T, bool = std::is_arithmetic<T>::value> struct ComputeType {}; | ||||
| 
 | ||||
| /// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
 | ||||
| template<class Shape> | ||||
| using TPoint = typename PointType<remove_cvref_t<Shape>>::Type; | ||||
| /// A compute type is introduced to hold the results of computations on
 | ||||
| /// coordinates and points. It should be larger in range than the coordinate 
 | ||||
| /// type or the range of coordinates should be limited to not loose precision.
 | ||||
| template<class GeomClass> struct ComputeType<GeomClass, false> { | ||||
|     using Type = typename ComputeType<TCoord<GeomClass>>::Type; | ||||
| }; | ||||
| 
 | ||||
| /// libnest2d will choose a default compute type for various coordinate types
 | ||||
| /// if the backend has not specified anything.
 | ||||
| template<class T> struct DoublePrecision { using Type = T; }; | ||||
| template<> struct DoublePrecision<int8_t> { using Type = int16_t; }; | ||||
| template<> struct DoublePrecision<int16_t> { using Type = int32_t; }; | ||||
| template<> struct DoublePrecision<int32_t> { using Type = int64_t; }; | ||||
| template<> struct DoublePrecision<float> { using Type = double; }; | ||||
| template<> struct DoublePrecision<double> { using Type = long double; }; | ||||
| template<class I> struct ComputeType<I, true> { | ||||
|     using Type = typename DoublePrecision<I>::Type; | ||||
| }; | ||||
| 
 | ||||
| template<class RawShape> struct CountourType { using Type = RawShape; }; | ||||
| 
 | ||||
| template<class RawShape> | ||||
| using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type; | ||||
| 
 | ||||
| /// TCompute<T> shorthand for `typename ComputeType<T>::Type`
 | ||||
| template<class T> using TCompute = typename ComputeType<remove_cvref_t<T>>::Type; | ||||
| 
 | ||||
| /// A meta function to derive a container type for holes in a polygon
 | ||||
| template<class RawShape> | ||||
| struct HolesContainer { using Type = std::vector<TContour<RawShape>>;  }; | ||||
| 
 | ||||
| /// Shorthand for `typename HolesContainer<RawShape>::Type`
 | ||||
| template<class RawShape> | ||||
| using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type; | ||||
| 
 | ||||
| /*
 | ||||
|  * TContour, TPoint, TCoord and TCompute should be usable for any type for which | ||||
|  * it makes sense. For example, the point type could be derived from the contour, | ||||
|  * the polygon and (or) the multishape as well. The coordinate type also and | ||||
|  * including the point type. TCoord<Polygon>, TCoord<Path>, TCoord<Point> are | ||||
|  * all valid types and derives the coordinate type of template argument Polygon, | ||||
|  * Path and Point. This is also true for TCompute, but it can also take the  | ||||
|  * coordinate type as argument. | ||||
|  */ | ||||
| 
 | ||||
| template<class RawShape> | ||||
| struct LastPointIsFirst { static const bool Value = true; }; | ||||
| /*
 | ||||
|  * A Multi shape concept is also introduced. A multi shape is something that | ||||
|  * can contain the result of an operation where the input is one polygon and  | ||||
|  * the result could be many polygons or path -> paths. The MultiShape should be | ||||
|  * a container type. If the backend does not specialize the MultiShape template, | ||||
|  * a default multi shape container will be used. | ||||
|  */ | ||||
| 
 | ||||
| /// The default multi shape container.
 | ||||
| template<class S> struct DefaultMultiShape: public std::vector<S> { | ||||
|     using Tag = MultiPolygonTag; | ||||
|     template<class...Args> DefaultMultiShape(Args&&...args): | ||||
|         std::vector<S>(std::forward<Args>(args)...) {} | ||||
| }; | ||||
| 
 | ||||
| /// The MultiShape Type trait which gets the container type for a geometry type.
 | ||||
| template<class S> struct MultiShape { using Type = DefaultMultiShape<S>; }; | ||||
| 
 | ||||
| /// use TMultiShape<S> instead of `typename MultiShape<S>::Type`
 | ||||
| template<class S>  | ||||
| using TMultiShape = typename MultiShape<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| // A specialization of ContourType to work with the default multishape type
 | ||||
| template<class S> struct ContourType<DefaultMultiShape<S>> { | ||||
|     using Type = typename ContourType<S>::Type; | ||||
| }; | ||||
| 
 | ||||
| enum class Orientation { | ||||
|     CLOCKWISE, | ||||
|  | @ -59,6 +139,11 @@ struct OrientationType { | |||
|     static const Orientation Value = Orientation::CLOCKWISE; | ||||
| }; | ||||
| 
 | ||||
| template<class T> inline /*constexpr*/ bool is_clockwise() {  | ||||
|     return OrientationType<TContour<T>>::Value == Orientation::CLOCKWISE;  | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief A point pair base class for other point pairs (segment, box, ...). | ||||
|  * \tparam RawPoint The actual point type to use. | ||||
|  | @ -69,21 +154,6 @@ struct PointPair { | |||
|     RawPoint p2; | ||||
| }; | ||||
| 
 | ||||
| struct PointTag {}; | ||||
| struct PolygonTag {}; | ||||
| struct PathTag {}; | ||||
| struct MultiPolygonTag {}; | ||||
| struct BoxTag {}; | ||||
| struct CircleTag {}; | ||||
| 
 | ||||
| /// Meta-functions to derive the tags
 | ||||
| template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; }; | ||||
| template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| template<class S> struct MultiShape { using Type = std::vector<S>; }; | ||||
| template<class S> | ||||
| using TMultiShape =typename MultiShape<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief An abstraction of a box; | ||||
|  */ | ||||
|  | @ -114,11 +184,16 @@ public: | |||
| 
 | ||||
|     inline RawPoint center() const BP2D_NOEXCEPT; | ||||
| 
 | ||||
|     inline double area() const BP2D_NOEXCEPT { | ||||
|         return double(width()*height()); | ||||
|     template<class Unit = TCompute<RawPoint>>  | ||||
|     inline Unit area() const BP2D_NOEXCEPT { | ||||
|         return Unit(width())*height(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<class S> struct PointType<_Box<S>> {  | ||||
|     using Type = typename _Box<S>::PointType;  | ||||
| }; | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| class _Circle { | ||||
|     RawPoint center_; | ||||
|  | @ -129,7 +204,6 @@ public: | |||
|     using PointType = RawPoint; | ||||
| 
 | ||||
|     _Circle() = default; | ||||
| 
 | ||||
|     _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} | ||||
| 
 | ||||
|     inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } | ||||
|  | @ -137,12 +211,16 @@ public: | |||
| 
 | ||||
|     inline double radius() const BP2D_NOEXCEPT { return radius_; } | ||||
|     inline void radius(double r) { radius_ = r; } | ||||
| 
 | ||||
|      | ||||
|     inline double area() const BP2D_NOEXCEPT { | ||||
|         return 2.0*Pi*radius_*radius_; | ||||
|         return Pi_2 * radius_ * radius_; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<class S> struct PointType<_Circle<S>> { | ||||
|     using Type = typename _Circle<S>::PointType; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief An abstraction of a directed line segment with two points. | ||||
|  */ | ||||
|  | @ -185,7 +263,12 @@ public: | |||
|     inline Radians angleToXaxis() const; | ||||
| 
 | ||||
|     /// The length of the segment in the measure of the coordinate system.
 | ||||
|     inline double length(); | ||||
|     template<class Unit = TCompute<RawPoint>> inline Unit sqlength() const; | ||||
|      | ||||
| }; | ||||
| 
 | ||||
| template<class S> struct PointType<_Segment<S>> {  | ||||
|     using Type = typename _Circle<S>::PointType;  | ||||
| }; | ||||
| 
 | ||||
| // This struct serves almost as a namespace. The only difference is that is can
 | ||||
|  | @ -216,33 +299,56 @@ inline TCoord<RawPoint>& y(RawPoint& p) | |||
|     return p.y(); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) | ||||
| template<class RawPoint, class Unit = TCompute<RawPoint>> | ||||
| inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2) | ||||
| { | ||||
|     static_assert(always_false<RawPoint>::value, | ||||
|                   "PointLike::distance(point, point) unimplemented!"); | ||||
|     return 0; | ||||
|     auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2)); | ||||
|     Unit a = (x2 - x1), b = (y2 - y1); | ||||
|     return a * a + b * b; | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline double distance(const RawPoint& /*p1*/, | ||||
|                        const _Segment<RawPoint>& /*s*/) | ||||
| inline double distance(const RawPoint& p1, const RawPoint& p2) | ||||
| { | ||||
|     static_assert(always_false<RawPoint>::value, | ||||
|                   "PointLike::distance(point, segment) unimplemented!"); | ||||
|     return 0; | ||||
|     return std::sqrt(squaredDistance<RawPoint, double>(p1, p2)); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline std::pair<TCoord<RawPoint>, bool> horizontalDistance( | ||||
| // create perpendicular vector
 | ||||
| template<class Pt> inline Pt perp(const Pt& p)  | ||||
| {  | ||||
|     return Pt(y(p), -x(p)); | ||||
| } | ||||
| 
 | ||||
| template<class Pt, class Unit = TCompute<Pt>>  | ||||
| inline Unit dotperp(const Pt& a, const Pt& b)  | ||||
| {  | ||||
|     return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b));  | ||||
| } | ||||
| 
 | ||||
| // dot product
 | ||||
| template<class Pt, class Unit = TCompute<Pt>>  | ||||
| inline Unit dot(const Pt& a, const Pt& b)  | ||||
| { | ||||
|     return Unit(x(a)) * x(b) + Unit(y(a)) * y(b); | ||||
| } | ||||
| 
 | ||||
| // squared vector magnitude
 | ||||
| template<class Pt, class Unit = TCompute<Pt>>  | ||||
| inline Unit magnsq(const Pt& p)  | ||||
| { | ||||
|     return  Unit(x(p)) * x(p) + Unit(y(p)) * y(p); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint, class Unit = TCompute<RawPoint>> | ||||
| inline std::pair<Unit, bool> horizontalDistance( | ||||
|         const RawPoint& p, const _Segment<RawPoint>& s) | ||||
| { | ||||
|     using Unit = TCoord<RawPoint>; | ||||
|     auto x = pointlike::x(p), y = pointlike::y(p); | ||||
|     auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); | ||||
|     auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); | ||||
|     namespace pl = pointlike; | ||||
|     auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); | ||||
|     auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); | ||||
|     auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); | ||||
| 
 | ||||
|     TCoord<RawPoint> ret; | ||||
|     Unit ret; | ||||
| 
 | ||||
|     if( (y < y1 && y < y2) || (y > y1 && y > y2) ) | ||||
|         return {0, false}; | ||||
|  | @ -250,8 +356,7 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance( | |||
|         ret = std::min( x-x1, x -x2); | ||||
|     else if( (y == y1 && y == y2) && (x < x1 && x < x2)) | ||||
|         ret = -std::min(x1 - x, x2 - x); | ||||
|     else if(std::abs(y - y1) <= std::numeric_limits<Unit>::epsilon() && | ||||
|             std::abs(y - y2) <= std::numeric_limits<Unit>::epsilon()) | ||||
|     else if(y == y1 && y == y2) | ||||
|         ret = 0; | ||||
|     else | ||||
|         ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); | ||||
|  | @ -259,16 +364,16 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance( | |||
|     return {ret, true}; | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline std::pair<TCoord<RawPoint>, bool> verticalDistance( | ||||
| template<class RawPoint, class Unit = TCompute<RawPoint>> | ||||
| inline std::pair<Unit, bool> verticalDistance( | ||||
|         const RawPoint& p, const _Segment<RawPoint>& s) | ||||
| { | ||||
|     using Unit = TCoord<RawPoint>; | ||||
|     auto x = pointlike::x(p), y = pointlike::y(p); | ||||
|     auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); | ||||
|     auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); | ||||
|     namespace pl = pointlike; | ||||
|     auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); | ||||
|     auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); | ||||
|     auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); | ||||
| 
 | ||||
|     TCoord<RawPoint> ret; | ||||
|     Unit ret; | ||||
| 
 | ||||
|     if( (x < x1 && x < x2) || (x > x1 && x > x2) ) | ||||
|         return {0, false}; | ||||
|  | @ -276,8 +381,7 @@ inline std::pair<TCoord<RawPoint>, bool> verticalDistance( | |||
|         ret = std::min( y-y1, y -y2); | ||||
|     else if( (x == x1 && x == x2) && (y < y1 && y < y2)) | ||||
|         ret = -std::min(y1 - y, y2 - y); | ||||
|     else if(std::abs(x - x1) <= std::numeric_limits<Unit>::epsilon() && | ||||
|             std::abs(x - x2) <= std::numeric_limits<Unit>::epsilon()) | ||||
|     else if(x == x1 && x == x2) | ||||
|         ret = 0; | ||||
|     else | ||||
|         ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); | ||||
|  | @ -333,9 +437,10 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const | |||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline double _Segment<RawPoint>::length() | ||||
| template<class Unit> | ||||
| inline Unit _Segment<RawPoint>::sqlength() const | ||||
| { | ||||
|     return pointlike::distance(first(), second()); | ||||
|     return pointlike::squaredDistance<RawPoint, Unit>(first(), second()); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
|  | @ -346,8 +451,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT { | |||
|     using Coord = TCoord<RawPoint>; | ||||
| 
 | ||||
|     RawPoint ret =  { // No rounding here, we dont know if these are int coords
 | ||||
|         static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ), | ||||
|         static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 ) | ||||
|         Coord( (getX(minc) + getX(maxc)) / Coord(2) ), | ||||
|         Coord( (getY(minc) + getY(maxc)) / Coord(2) ) | ||||
|     }; | ||||
| 
 | ||||
|     return ret; | ||||
|  | @ -362,9 +467,6 @@ enum class Formats { | |||
| // used in friend declarations and can be aliased at class scope.
 | ||||
| namespace shapelike { | ||||
| 
 | ||||
| template<class RawShape> | ||||
| using Shapes = TMultiShape<RawShape>; | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape create(const TContour<RawShape>& contour, | ||||
|                        const THolesContainer<RawShape>& holes) | ||||
|  | @ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) | |||
| template<class RawShape, class...Args> | ||||
| inline void addVertex(RawShape& sh, const PathTag&, Args...args) | ||||
| { | ||||
|     return sh.emplace_back(std::forward<Args>(args)...); | ||||
|     sh.emplace_back(std::forward<Args>(args)...); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape, class Fn> | ||||
|  | @ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) | |||
|                   "shapelike::unserialize() unimplemented!"); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline double area(const RawShape& /*sh*/, const PolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShape>::value, | ||||
|                   "shapelike::area() unimplemented!"); | ||||
|     return 0; | ||||
| } | ||||
| template<class Cntr, class Unit = double> | ||||
| inline Unit area(const Cntr& poly, const PathTag& ); | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) | ||||
|  | @ -556,14 +653,14 @@ inline bool touches( const TPoint<RawShape>& /*point*/, | |||
| 
 | ||||
| template<class RawShape> | ||||
| inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/, | ||||
|                                           const PolygonTag&) | ||||
|                                           const PathTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShape>::value, | ||||
|                   "shapelike::boundingBox(shape) unimplemented!"); | ||||
| } | ||||
| 
 | ||||
| template<class RawShapes> | ||||
| inline _Box<TPoint<typename RawShapes::value_type>> | ||||
| inline _Box<TPoint<RawShapes>> | ||||
| boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShapes>::value, | ||||
|  | @ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) | |||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShape>::value, | ||||
|                   "shapelike::convexHull(shape) unimplemented!"); | ||||
|     return RawShape(); | ||||
| } | ||||
| inline RawShape convexHull(const RawShape& sh, const PathTag&); | ||||
| 
 | ||||
| template<class RawShapes> | ||||
| inline typename RawShapes::value_type | ||||
| convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShapes>::value, | ||||
|                   "shapelike::convexHull(shapes) unimplemented!"); | ||||
|     return typename RawShapes::value_type(); | ||||
| } | ||||
| template<class RawShapes, class S = typename RawShapes::value_type> | ||||
| inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) | ||||
|  | @ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) { | |||
| template<class RawShape, class...Args> | ||||
| inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) | ||||
| { | ||||
|     return addVertex(contour(sh), PathTag(), std::forward<Args>(args)...); | ||||
|     addVertex(contour(sh), PathTag(), std::forward<Args>(args)...); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape, class...Args> // Tag dispatcher
 | ||||
| inline void addVertex(RawShape& sh, Args...args) | ||||
| { | ||||
|     return addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...); | ||||
|     addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline _Box<TPoint<RawShape>> boundingBox(const RawShape& poly, const PolygonTag&) | ||||
| { | ||||
|     return boundingBox(contour(poly), PathTag()); | ||||
| } | ||||
| 
 | ||||
| template<class Box> | ||||
|  | @ -786,7 +878,7 @@ inline _Box<TPoint<S>> boundingBox(const S& sh) | |||
| template<class Box> | ||||
| inline double area(const Box& box, const BoxTag& ) | ||||
| { | ||||
|     return box.area(); | ||||
|     return box.template area<double>(); | ||||
| } | ||||
| 
 | ||||
| template<class Circle> | ||||
|  | @ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& ) | |||
|     return circ.area(); | ||||
| } | ||||
| 
 | ||||
| template<class Cntr, class Unit> | ||||
| inline Unit area(const Cntr& poly, const PathTag& ) | ||||
| { | ||||
|     namespace sl = shapelike; | ||||
|     if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0; | ||||
|    | ||||
|     Unit a = 0; | ||||
|     for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly));  | ||||
|          i < sl::cend(poly); ++i) | ||||
|     { | ||||
|         auto xj = Unit(getX(*j)), yj = Unit(getY(*j)); | ||||
|         auto xi = Unit(getX(*i)), yi = Unit(getY(*i)); | ||||
|         a += (xj + xi) *  (yj - yi); | ||||
|         j = i; | ||||
|     } | ||||
|     a /= 2; | ||||
|     return is_clockwise<Cntr>() ? a : -a; | ||||
| } | ||||
| 
 | ||||
| template<class S> inline double area(const S& poly, const PolygonTag& ) | ||||
| { | ||||
|     auto hls = holes(poly); | ||||
|     return std::accumulate(hls.begin(), hls.end(),  | ||||
|                            area(contour(poly), PathTag()), | ||||
|                            [](double a, const TContour<S> &h){ | ||||
|         return a + area(h, PathTag());     | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> // Dispatching function
 | ||||
| inline double area(const RawShape& sh) | ||||
| { | ||||
|  | @ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&) | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape convexHull(const RawShape& sh, const PolygonTag&) | ||||
| { | ||||
|     return create<RawShape>(convexHull(contour(sh), PathTag())); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline auto convexHull(const RawShape& sh) | ||||
|     -> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
 | ||||
|  | @ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh) | |||
|     return convexHull(sh, Tag<RawShape>()); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape convexHull(const RawShape& sh, const PathTag&) | ||||
| { | ||||
|     using Unit = TCompute<RawShape>; | ||||
|     using Point = TPoint<RawShape>; | ||||
|     namespace sl = shapelike; | ||||
|      | ||||
|     size_t edges = sl::cend(sh) - sl::cbegin(sh); | ||||
|     if(edges <= 3) return {}; | ||||
|      | ||||
|     bool closed = false; | ||||
|     std::vector<Point> U, L; | ||||
|     U.reserve(1 + edges / 2); L.reserve(1 + edges / 2); | ||||
|      | ||||
|     std::vector<Point> pts; pts.reserve(edges); | ||||
|     std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts)); | ||||
|      | ||||
|     auto fpt = pts.front(), lpt = pts.back(); | ||||
|     if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) {  | ||||
|         closed = true; pts.pop_back(); | ||||
|     } | ||||
|      | ||||
|     std::sort(pts.begin(), pts.end(),  | ||||
|               [](const Point& v1, const Point& v2) | ||||
|     { | ||||
|         Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); | ||||
|         return x1 == x2 ? y1 < y2 : x1 < x2; | ||||
|     }); | ||||
|      | ||||
|     auto dir = [](const Point& p, const Point& q, const Point& r) { | ||||
|         return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - | ||||
|                (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); | ||||
|     }; | ||||
|      | ||||
|     auto ik = pts.begin(); | ||||
|      | ||||
|     while(ik != pts.end()) { | ||||
|          | ||||
|         while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0)  | ||||
|             U.pop_back(); | ||||
|         while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0)  | ||||
|             L.pop_back(); | ||||
|          | ||||
|         U.emplace_back(*ik); | ||||
|         L.emplace_back(*ik); | ||||
|          | ||||
|         ++ik; | ||||
|     } | ||||
|      | ||||
|     RawShape ret; reserve(ret, U.size() + L.size()); | ||||
|     if(is_clockwise<RawShape>()) { | ||||
|         for(auto it = U.begin(); it != std::prev(U.end()); ++it)  | ||||
|             addVertex(ret, *it);   | ||||
|         for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it)  | ||||
|             addVertex(ret, *it); | ||||
|         if(closed) addVertex(ret, *std::prev(L.rend())); | ||||
|     } else { | ||||
|         for(auto it = L.begin(); it != std::prev(L.end()); ++it)  | ||||
|             addVertex(ret, *it);   | ||||
|         for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it)  | ||||
|             addVertex(ret, *it);   | ||||
|         if(closed) addVertex(ret, *std::prev(U.rend())); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| template<class RawShapes, class S> | ||||
| inline S convexHull(const RawShapes& sh, const MultiPolygonTag&) | ||||
| { | ||||
|     namespace sl = shapelike; | ||||
|     S cntr; | ||||
|     for(auto& poly : sh)  | ||||
|         for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it)  | ||||
|             addVertex(cntr, *it); | ||||
|      | ||||
|     return convexHull(cntr, Tag<S>()); | ||||
| } | ||||
| 
 | ||||
| template<class TP, class TC> | ||||
| inline bool isInside(const TP& point, const TC& circ, | ||||
|                      const PointTag&, const CircleTag&) | ||||
| { | ||||
|     return pointlike::distance(point, circ.center()) < circ.radius(); | ||||
|     auto r = circ.radius(); | ||||
|     return pointlike::squaredDistance(point, circ.center()) < r * r; | ||||
| } | ||||
| 
 | ||||
| template<class TP, class TB> | ||||
|  | @ -972,6 +1179,9 @@ template<class RawShape> inline bool isConvex(const RawShape& sh) // dispatch | |||
|     using Segment = _Segment<Point>; \ | ||||
|     using Polygons = TMultiShape<T> | ||||
| 
 | ||||
| namespace sl = shapelike; | ||||
| namespace pl = pointlike; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // GEOMETRY_TRAITS_HPP
 | ||||
|  |  | |||
|  | @ -1,26 +1,22 @@ | |||
| #ifndef GEOMETRIES_NOFITPOLYGON_HPP | ||||
| #define GEOMETRIES_NOFITPOLYGON_HPP | ||||
| 
 | ||||
| #include "geometry_traits.hpp" | ||||
| #include <algorithm> | ||||
| #include <functional> | ||||
| #include <vector> | ||||
| #include <iterator> | ||||
| 
 | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| namespace __nfp { | ||||
| // Do not specialize this...
 | ||||
| template<class RawShape> | ||||
| template<class RawShape, class Unit = TCompute<RawShape>> | ||||
| inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2) | ||||
| { | ||||
|     using Coord = TCoord<TPoint<RawShape>>; | ||||
|     Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); | ||||
|     auto diff = y1 - y2; | ||||
|     if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon()) | ||||
|         return x1 < x2; | ||||
| 
 | ||||
|     return diff < 0; | ||||
|     Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); | ||||
|     return y1 == y2 ? x1 < x2 : y1 < y2; | ||||
| } | ||||
| 
 | ||||
| template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>> | ||||
|  | @ -202,7 +198,7 @@ inline TPoint<RawShape> referenceVertex(const RawShape& sh) | |||
|  * convex as well in this case. | ||||
|  * | ||||
|  */ | ||||
| template<class RawShape> | ||||
| template<class RawShape, class Ratio = double> | ||||
| inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, | ||||
|                                          const RawShape& other) | ||||
| { | ||||
|  | @ -238,12 +234,62 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, | |||
|             ++first; ++next; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Sort the edges by angle to X axis.
 | ||||
|     std::sort(edgelist.begin(), edgelist.end(), | ||||
|               [](const Edge& e1, const Edge& e2) | ||||
|     | ||||
|     std::sort(edgelist.begin(), edgelist.end(),  | ||||
|               [](const Edge& e1, const Edge& e2)  | ||||
|     { | ||||
|         return e1.angleToXaxis() > e2.angleToXaxis(); | ||||
|         Vertex ax(1, 0); // Unit vector for the X axis
 | ||||
|          | ||||
|         // get cectors from the edges
 | ||||
|         Vertex p1 = e1.second() - e1.first(); | ||||
|         Vertex p2 = e2.second() - e2.first(); | ||||
| 
 | ||||
|         // Quadrant mapping array. The quadrant of a vector can be determined
 | ||||
|         // from the dot product of the vector and its perpendicular pair
 | ||||
|         // with the unit vector X axis. The products will carry the values
 | ||||
|         // lcos = dot(p, ax) = l * cos(phi) and
 | ||||
|         // lsin = -dotperp(p, ax) = l * sin(phi) where
 | ||||
|         // l is the length of vector p. From the signs of these values we can
 | ||||
|         // construct an index which has the sign of lcos as MSB and the
 | ||||
|         // sign of lsin as LSB. This index can be used to retrieve the actual
 | ||||
|         // quadrant where vector p resides using the following map:
 | ||||
|         // (+ is 0, - is 1)
 | ||||
|         // cos | sin | decimal | quadrant
 | ||||
|         //  +  |  +  |    0    |    0
 | ||||
|         //  +  |  -  |    1    |    3
 | ||||
|         //  -  |  +  |    2    |    1
 | ||||
|         //  -  |  -  |    3    |    2
 | ||||
|         std::array<int, 4> quadrants {0, 3, 1, 2 }; | ||||
| 
 | ||||
|         std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
 | ||||
| 
 | ||||
|         using TDots = std::array<TCompute<Vertex>, 2>; | ||||
|         TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) }; | ||||
|         TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) }; | ||||
| 
 | ||||
|         // Construct the quadrant indices for p1 and p2
 | ||||
|         for(size_t i = 0; i < 2; ++i) | ||||
|             if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3; | ||||
|             else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2; | ||||
|             else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; | ||||
|              | ||||
|         if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
 | ||||
|             auto lsq1 = pl::magnsq(p1);     // squared magnitudes, avoid sqrt
 | ||||
|             auto lsq2 = pl::magnsq(p2);     // squared magnitudes, avoid sqrt
 | ||||
| 
 | ||||
|             // We will actually compare l^2 * cos^2(phi) which saturates the
 | ||||
|             // cos function. But with the quadrant info we can get the sign back
 | ||||
|             int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; | ||||
|              | ||||
|             // If Ratio is an actual rational type, there is no precision loss
 | ||||
|             auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; | ||||
|             auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; | ||||
|              | ||||
|             return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2; | ||||
|         } | ||||
|          | ||||
|         // If in different quadrants, compare the quadrant indices only.
 | ||||
|         return q[0] > q[1]; | ||||
|     }); | ||||
| 
 | ||||
|     __nfp::buildPolygon(edgelist, rsh, top_nfp); | ||||
|  | @ -253,456 +299,9 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, | |||
| 
 | ||||
| template<class RawShape> | ||||
| NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary, | ||||
|                                            const RawShape& cother) | ||||
|                                     const RawShape& cother) | ||||
| { | ||||
| 
 | ||||
|     // Algorithms are from the original algorithm proposed in paper:
 | ||||
|     // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
 | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Algorithm 1: Obtaining the minkowski sum
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     // I guess this is not a full minkowski sum of the two input polygons by
 | ||||
|     // definition. This yields a subset that is compatible with the next 2
 | ||||
|     // algorithms.
 | ||||
| 
 | ||||
|     using Result = NfpResult<RawShape>; | ||||
|     using Vertex = TPoint<RawShape>; | ||||
|     using Coord = TCoord<Vertex>; | ||||
|     using Edge = _Segment<Vertex>; | ||||
|     namespace sl = shapelike; | ||||
|     using std::signbit; | ||||
|     using std::sort; | ||||
|     using std::vector; | ||||
|     using std::ref; | ||||
|     using std::reference_wrapper; | ||||
| 
 | ||||
|     // TODO The original algorithms expects the stationary polygon in
 | ||||
|     // counter clockwise and the orbiter in clockwise order.
 | ||||
|     // So for preventing any further complication, I will make the input
 | ||||
|     // the way it should be, than make my way around the orientations.
 | ||||
| 
 | ||||
|     // Reverse the stationary contour to counter clockwise
 | ||||
|     auto stcont = sl::contour(cstationary); | ||||
|     { | ||||
|         std::reverse(sl::begin(stcont), sl::end(stcont)); | ||||
|         stcont.pop_back(); | ||||
|         auto it = std::min_element(sl::begin(stcont), sl::end(stcont), | ||||
|                                [](const Vertex& v1, const Vertex& v2) { | ||||
|             return getY(v1) < getY(v2); | ||||
|         }); | ||||
|         std::rotate(sl::begin(stcont), it, sl::end(stcont)); | ||||
|         sl::addVertex(stcont, sl::front(stcont)); | ||||
|     } | ||||
|     RawShape stationary; | ||||
|     sl::contour(stationary) = stcont; | ||||
| 
 | ||||
|     // Reverse the orbiter contour to counter clockwise
 | ||||
|     auto orbcont = sl::contour(cother); | ||||
|     { | ||||
|         std::reverse(orbcont.begin(), orbcont.end()); | ||||
| 
 | ||||
|         // Step 1: Make the orbiter reverse oriented
 | ||||
| 
 | ||||
|         orbcont.pop_back(); | ||||
|         auto it = std::min_element(orbcont.begin(), orbcont.end(), | ||||
|                               [](const Vertex& v1, const Vertex& v2) { | ||||
|             return getY(v1) < getY(v2); | ||||
|         }); | ||||
| 
 | ||||
|         std::rotate(orbcont.begin(), it, orbcont.end()); | ||||
|         orbcont.emplace_back(orbcont.front()); | ||||
| 
 | ||||
|         for(auto &v : orbcont) v = -v; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Copy the orbiter (contour only), we will have to work on it
 | ||||
|     RawShape orbiter; | ||||
|     sl::contour(orbiter) = orbcont; | ||||
| 
 | ||||
|     // An edge with additional data for marking it
 | ||||
|     struct MarkedEdge { | ||||
|         Edge e; Radians turn_angle = 0; bool is_turning_point = false; | ||||
|         MarkedEdge() = default; | ||||
|         MarkedEdge(const Edge& ed, Radians ta, bool tp): | ||||
|             e(ed), turn_angle(ta), is_turning_point(tp) {} | ||||
| 
 | ||||
|         // debug
 | ||||
|         std::string label; | ||||
|     }; | ||||
| 
 | ||||
|     // Container for marked edges
 | ||||
|     using EdgeList = vector<MarkedEdge>; | ||||
| 
 | ||||
|     EdgeList A, B; | ||||
| 
 | ||||
|     // This is how an edge list is created from the polygons
 | ||||
|     auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { | ||||
|         auto& poly = sl::contour(ppoly); | ||||
| 
 | ||||
|         L.reserve(sl::contourVertexCount(poly)); | ||||
| 
 | ||||
|         if(dir > 0) { | ||||
|             auto it = poly.begin(); | ||||
|             auto nextit = std::next(it); | ||||
| 
 | ||||
|             double turn_angle = 0; | ||||
|             bool is_turn_point = false; | ||||
| 
 | ||||
|             while(nextit != poly.end()) { | ||||
|                 L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); | ||||
|                 it++; nextit++; | ||||
|             } | ||||
|         } else { | ||||
|             auto it = sl::rbegin(poly); | ||||
|             auto nextit = std::next(it); | ||||
| 
 | ||||
|             double turn_angle = 0; | ||||
|             bool is_turn_point = false; | ||||
| 
 | ||||
|             while(nextit != sl::rend(poly)) { | ||||
|                 L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); | ||||
|                 it++; nextit++; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         auto getTurnAngle = [](const Edge& e1, const Edge& e2) { | ||||
|             auto phi = e1.angleToXaxis(); | ||||
|             auto phi_prev = e2.angleToXaxis(); | ||||
|             auto turn_angle = phi-phi_prev; | ||||
|             if(turn_angle > Pi) turn_angle -= TwoPi; | ||||
|             if(turn_angle < -Pi) turn_angle += TwoPi; | ||||
|             return turn_angle; | ||||
|         }; | ||||
| 
 | ||||
|         auto eit = L.begin(); | ||||
|         auto enext = std::next(eit); | ||||
| 
 | ||||
|         eit->turn_angle = getTurnAngle(L.front().e, L.back().e); | ||||
| 
 | ||||
|         while(enext != L.end()) { | ||||
|             enext->turn_angle = getTurnAngle( enext->e, eit->e); | ||||
|             eit->is_turning_point = | ||||
|                     signbit(enext->turn_angle) != signbit(eit->turn_angle); | ||||
|             ++eit; ++enext; | ||||
|         } | ||||
| 
 | ||||
|         L.back().is_turning_point = signbit(L.back().turn_angle) != | ||||
|                                     signbit(L.front().turn_angle); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     // Step 2: Fill the edgelists
 | ||||
|     fillEdgeList(A, stationary, 1); | ||||
|     fillEdgeList(B, orbiter, 1); | ||||
| 
 | ||||
|     int i = 1; | ||||
|     for(MarkedEdge& me : A) { | ||||
|         std::cout << "a" << i << ":\n\t" | ||||
|                   << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" | ||||
|                   << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" | ||||
|                   << "Turning point: " << (me.is_turning_point ? "yes" : "no") | ||||
|                   << std::endl; | ||||
| 
 | ||||
|         me.label = "a"; me.label += std::to_string(i); | ||||
|         i++; | ||||
|     } | ||||
| 
 | ||||
|     i = 1; | ||||
|     for(MarkedEdge& me : B) { | ||||
|         std::cout << "b" << i << ":\n\t" | ||||
|                   << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" | ||||
|                   << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" | ||||
|                   << "Turning point: " << (me.is_turning_point ? "yes" : "no") | ||||
|                   << std::endl; | ||||
|         me.label = "b"; me.label += std::to_string(i); | ||||
|         i++; | ||||
|     } | ||||
| 
 | ||||
|     // A reference to a marked edge that also knows its container
 | ||||
|     struct MarkedEdgeRef { | ||||
|         reference_wrapper<MarkedEdge> eref; | ||||
|         reference_wrapper<vector<MarkedEdgeRef>> container; | ||||
|         Coord dir = 1;  // Direction modifier
 | ||||
| 
 | ||||
|         inline Radians angleX() const { return eref.get().e.angleToXaxis(); } | ||||
|         inline const Edge& edge() const { return eref.get().e; } | ||||
|         inline Edge& edge() { return eref.get().e; } | ||||
|         inline bool isTurningPoint() const { | ||||
|             return eref.get().is_turning_point; | ||||
|         } | ||||
|         inline bool isFrom(const vector<MarkedEdgeRef>& cont ) { | ||||
|             return &(container.get()) == &cont; | ||||
|         } | ||||
|         inline bool eq(const MarkedEdgeRef& mr) { | ||||
|             return &(eref.get()) == &(mr.eref.get()); | ||||
|         } | ||||
| 
 | ||||
|         MarkedEdgeRef(reference_wrapper<MarkedEdge> er, | ||||
|                       reference_wrapper<vector<MarkedEdgeRef>> ec): | ||||
|             eref(er), container(ec), dir(1) {} | ||||
| 
 | ||||
|         MarkedEdgeRef(reference_wrapper<MarkedEdge> er, | ||||
|                       reference_wrapper<vector<MarkedEdgeRef>> ec, | ||||
|                       Coord d): | ||||
|             eref(er), container(ec), dir(d) {} | ||||
|     }; | ||||
| 
 | ||||
|     using EdgeRefList = vector<MarkedEdgeRef>; | ||||
| 
 | ||||
|     // Comparing two marked edges
 | ||||
|     auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { | ||||
|         return e1.angleX() < e2.angleX(); | ||||
|     }; | ||||
| 
 | ||||
|     EdgeRefList Aref, Bref;     // We create containers for the references
 | ||||
|     Aref.reserve(A.size()); Bref.reserve(B.size()); | ||||
| 
 | ||||
|     // Fill reference container for the stationary polygon
 | ||||
|     std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { | ||||
|         Aref.emplace_back( ref(me), ref(Aref) ); | ||||
|     }); | ||||
| 
 | ||||
|     // Fill reference container for the orbiting polygon
 | ||||
|     std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { | ||||
|         Bref.emplace_back( ref(me), ref(Bref) ); | ||||
|     }); | ||||
| 
 | ||||
|     auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
 | ||||
|             (const EdgeRefList& Q, const EdgeRefList& R, bool positive) | ||||
|     { | ||||
| 
 | ||||
|         // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
 | ||||
|         // Sort the containers of edge references and merge them.
 | ||||
|         // Q could be sorted only once and be reused here but we would still
 | ||||
|         // need to merge it with sorted(R).
 | ||||
| 
 | ||||
|         EdgeRefList merged; | ||||
|         EdgeRefList S, seq; | ||||
|         merged.reserve(Q.size() + R.size()); | ||||
| 
 | ||||
|         merged.insert(merged.end(), R.begin(), R.end()); | ||||
|         std::stable_sort(merged.begin(), merged.end(), sortfn); | ||||
|         merged.insert(merged.end(), Q.begin(), Q.end()); | ||||
|         std::stable_sort(merged.begin(), merged.end(), sortfn); | ||||
| 
 | ||||
|         // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
 | ||||
|         // we don't use i, instead, q is an iterator into Q. k would be an index
 | ||||
|         // into the merged sequence but we use "it" as an iterator for that
 | ||||
| 
 | ||||
|         // here we obtain references for the containers for later comparisons
 | ||||
|         const auto& Rcont = R.begin()->container.get(); | ||||
|         const auto& Qcont = Q.begin()->container.get(); | ||||
| 
 | ||||
|         // Set the initial direction
 | ||||
|         Coord dir = 1; | ||||
| 
 | ||||
|         // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
 | ||||
|         if(positive) { | ||||
|             auto q = Q.begin(); | ||||
|             S.emplace_back(*q); | ||||
| 
 | ||||
|             // Roughly step 3
 | ||||
| 
 | ||||
|             std::cout << "merged size: " << merged.size() << std::endl; | ||||
|             auto mit = merged.begin(); | ||||
|             for(bool finish = false; !finish && q != Q.end();) { | ||||
|                 ++q; // "Set i = i + 1"
 | ||||
| 
 | ||||
|                 while(!finish && mit != merged.end()) { | ||||
|                     if(mit->isFrom(Rcont)) { | ||||
|                         auto s = *mit; | ||||
|                         s.dir = dir; | ||||
|                         S.emplace_back(s); | ||||
|                     } | ||||
| 
 | ||||
|                     if(mit->eq(*q)) { | ||||
|                         S.emplace_back(*q); | ||||
|                         if(mit->isTurningPoint()) dir = -dir; | ||||
|                         if(q == Q.begin()) finish = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     mit += dir; | ||||
|     //                __nfp::advance(mit, merged, dir > 0);
 | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             auto q = Q.rbegin(); | ||||
|             S.emplace_back(*q); | ||||
| 
 | ||||
|             // Roughly step 3
 | ||||
| 
 | ||||
|             std::cout << "merged size: " << merged.size() << std::endl; | ||||
|             auto mit = merged.begin(); | ||||
|             for(bool finish = false; !finish && q != Q.rend();) { | ||||
|                 ++q; // "Set i = i + 1"
 | ||||
| 
 | ||||
|                 while(!finish && mit != merged.end()) { | ||||
|                     if(mit->isFrom(Rcont)) { | ||||
|                         auto s = *mit; | ||||
|                         s.dir = dir; | ||||
|                         S.emplace_back(s); | ||||
|                     } | ||||
| 
 | ||||
|                     if(mit->eq(*q)) { | ||||
|                         S.emplace_back(*q); | ||||
|                         S.back().dir = -1; | ||||
|                         if(mit->isTurningPoint()) dir = -dir; | ||||
|                         if(q == Q.rbegin()) finish = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     mit += dir; | ||||
|             //                __nfp::advance(mit, merged, dir > 0);
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         // Step 4:
 | ||||
| 
 | ||||
|         // "Let starting edge r1 be in position si in sequence"
 | ||||
|         // whaaat? I guess this means the following:
 | ||||
|         auto it = S.begin(); | ||||
|         while(!it->eq(*R.begin())) ++it; | ||||
| 
 | ||||
|         // "Set j = 1, next = 2, direction = 1, seq1 = si"
 | ||||
|         // we don't use j, seq is expanded dynamically.
 | ||||
|         dir = 1; | ||||
|         auto next = std::next(R.begin()); seq.emplace_back(*it); | ||||
| 
 | ||||
|         // Step 5:
 | ||||
|         // "If all si edges have been allocated to seqj" should mean that
 | ||||
|         // we loop until seq has equal size with S
 | ||||
|         auto send = it; //it == S.begin() ? it : std::prev(it);
 | ||||
|         while(it != S.end()) { | ||||
|             ++it; if(it == S.end()) it = S.begin(); | ||||
|             if(it == send) break; | ||||
| 
 | ||||
|             if(it->isFrom(Qcont)) { | ||||
|                 seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
 | ||||
| 
 | ||||
|                 // "If si is a turning point in Q,
 | ||||
|                 // direction = - direction, next = next + direction"
 | ||||
|                 if(it->isTurningPoint()) { | ||||
|                     dir = -dir; | ||||
|                     next += dir; | ||||
| //                    __nfp::advance(next, R, dir > 0);
 | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
 | ||||
|                 // "j = j + 1, seqj = si, next = next + direction"
 | ||||
|                 seq.emplace_back(*it); | ||||
|                 next += dir; | ||||
| //                __nfp::advance(next, R, dir > 0);
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return seq; | ||||
|     }; | ||||
| 
 | ||||
|     std::vector<EdgeRefList> seqlist; | ||||
|     seqlist.reserve(Bref.size()); | ||||
| 
 | ||||
|     EdgeRefList Bslope = Bref;  // copy Bref, we will make a slope diagram
 | ||||
| 
 | ||||
|     // make the slope diagram of B
 | ||||
|     std::sort(Bslope.begin(), Bslope.end(), sortfn); | ||||
| 
 | ||||
|     auto slopeit = Bslope.begin(); // search for the first turning point
 | ||||
|     while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; | ||||
| 
 | ||||
|     if(slopeit == Bslope.end()) { | ||||
|         // no turning point means convex polygon.
 | ||||
|         seqlist.emplace_back(mink(Aref, Bref, true)); | ||||
|     } else { | ||||
|         int dir = 1; | ||||
| 
 | ||||
|         auto firstturn = Bref.begin(); | ||||
|         while(!firstturn->eq(*slopeit)) ++firstturn; | ||||
| 
 | ||||
|         assert(firstturn != Bref.end()); | ||||
| 
 | ||||
|         EdgeRefList bgroup; bgroup.reserve(Bref.size()); | ||||
|         bgroup.emplace_back(*slopeit); | ||||
| 
 | ||||
|         auto b_it = std::next(firstturn); | ||||
|         while(b_it != firstturn) { | ||||
|             if(b_it == Bref.end()) b_it = Bref.begin(); | ||||
| 
 | ||||
|             while(!slopeit->eq(*b_it)) { | ||||
|                 __nfp::advance(slopeit, Bslope, dir > 0); | ||||
|             } | ||||
| 
 | ||||
|             if(!slopeit->isTurningPoint()) { | ||||
|                 bgroup.emplace_back(*slopeit); | ||||
|             } else { | ||||
|                 if(!bgroup.empty()) { | ||||
|                     if(dir > 0) bgroup.emplace_back(*slopeit); | ||||
|                     for(auto& me : bgroup) { | ||||
|                         std::cout << me.eref.get().label << ", "; | ||||
|                     } | ||||
|                     std::cout << std::endl; | ||||
|                     seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); | ||||
|                     bgroup.clear(); | ||||
|                     if(dir < 0) bgroup.emplace_back(*slopeit); | ||||
|                 } else { | ||||
|                     bgroup.emplace_back(*slopeit); | ||||
|                 } | ||||
| 
 | ||||
|                 dir *= -1; | ||||
|             } | ||||
|             ++b_it; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| //    while(it != Bref.end()) // This is step 3 and step 4 in one loop
 | ||||
| //        if(it->isTurningPoint()) {
 | ||||
| //            R = {R.last, it++};
 | ||||
| //            auto seq = mink(Q, R, orientation);
 | ||||
| 
 | ||||
| //            // TODO step 6 (should be 5 shouldn't it?): linking edges from A
 | ||||
| //            // I don't get this step
 | ||||
| 
 | ||||
| //            seqlist.insert(seqlist.end(), seq.begin(), seq.end());
 | ||||
| //            orientation = !orientation;
 | ||||
| //        } else ++it;
 | ||||
| 
 | ||||
| //    if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
 | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Algorithm 2: breaking Minkowski sums into track line trips
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Algorithm 3: finding the boundary of the NFP from track line trips
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
|     for(auto& seq : seqlist) { | ||||
|         std::cout << "seqlist size: " << seq.size() << std::endl; | ||||
|         for(auto& s : seq) { | ||||
|             std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; | ||||
|         } | ||||
|         std::cout << std::endl; | ||||
|     } | ||||
| 
 | ||||
|     auto& seq = seqlist.front(); | ||||
|     RawShape rsh; | ||||
|     Vertex top_nfp; | ||||
|     std::vector<Edge> edgelist; edgelist.reserve(seq.size()); | ||||
|     for(auto& s : seq) { | ||||
|         edgelist.emplace_back(s.eref.get().e); | ||||
|     } | ||||
| 
 | ||||
|     __nfp::buildPolygon(edgelist, rsh, top_nfp); | ||||
| 
 | ||||
|     return Result(rsh, top_nfp); | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| // Specializable NFP implementation class. Specialize it if you have a faster
 | ||||
|  |  | |||
|  | @ -8,13 +8,10 @@ | |||
| #include <algorithm> | ||||
| #include <functional> | ||||
| 
 | ||||
| #include "geometry_traits.hpp" | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| namespace sl = shapelike; | ||||
| namespace pl = pointlike; | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief An item to be placed on a bin. | ||||
|  * | ||||
|  | @ -422,13 +419,9 @@ private: | |||
| 
 | ||||
|     static inline bool vsort(const Vertex& v1, const Vertex& v2) | ||||
|     { | ||||
|         Coord &&x1 = getX(v1), &&x2 = getX(v2); | ||||
|         Coord &&y1 = getY(v1), &&y2 = getY(v2); | ||||
|         auto diff = y1 - y2; | ||||
|         if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon()) | ||||
|             return x1 < x2; | ||||
| 
 | ||||
|         return diff < 0; | ||||
|         TCompute<Vertex> x1 = getX(v1), x2 = getX(v2); | ||||
|         TCompute<Vertex> y1 = getY(v1), y2 = getY(v2); | ||||
|         return y1 == y2 ? x1 < x2 : y1 < y2; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,8 @@ | |||
| #include <tuple> | ||||
| #include <functional> | ||||
| #include <limits> | ||||
| #include "common.hpp" | ||||
| 
 | ||||
| #include <libnest2d/common.hpp> | ||||
| 
 | ||||
| namespace libnest2d { namespace opt { | ||||
| 
 | ||||
|  | @ -60,6 +61,7 @@ enum class Method { | |||
|     L_SIMPLEX, | ||||
|     L_SUBPLEX, | ||||
|     G_GENETIC, | ||||
|     G_PARTICLE_SWARM | ||||
|     //...
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ else() | |||
|     target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt) | ||||
| endif() | ||||
| 
 | ||||
| #target_sources( NloptOptimizer INTERFACE | ||||
| #target_sources( nloptOptimizer INTERFACE | ||||
| #${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp | ||||
| #${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp | ||||
| #${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp | ||||
|  | @ -57,5 +57,5 @@ endif() | |||
| 
 | ||||
| target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) | ||||
| 
 | ||||
| # And finally plug the NloptOptimizer into libnest2d | ||||
| #target_link_libraries(libnest2d INTERFACE NloptOptimizer) | ||||
| # And finally plug the nloptOptimizer into libnest2d | ||||
| #target_link_libraries(libnest2d INTERFACE nloptOptimizer) | ||||
|  |  | |||
|  | @ -1,5 +0,0 @@ | |||
| find_package(Armadillo REQUIRED) | ||||
| 
 | ||||
| add_library(OptimlibOptimizer INTERFACE) | ||||
| target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS}) | ||||
| target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES}) | ||||
|  | @ -7,15 +7,15 @@ | |||
| 
 | ||||
| namespace libnest2d { namespace placers { | ||||
| 
 | ||||
| template<class T, class = T> struct Epsilon {}; | ||||
| template<class T, class = T> struct DefaultEpsilon {}; | ||||
| 
 | ||||
| template<class T> | ||||
| struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > { | ||||
| struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > { | ||||
|     static const T Value = 1; | ||||
| }; | ||||
| 
 | ||||
| template<class T> | ||||
| struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > { | ||||
| struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > { | ||||
|     static const T Value = 1e-3; | ||||
| }; | ||||
| 
 | ||||
|  | @ -24,7 +24,7 @@ struct BLConfig { | |||
|     DECLARE_MAIN_TYPES(RawShape); | ||||
| 
 | ||||
|     Coord min_obj_distance = 0; | ||||
|     Coord epsilon = Epsilon<Coord>::Value; | ||||
|     Coord epsilon = DefaultEpsilon<Coord>::Value; | ||||
|     bool allow_rotations = false; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -103,14 +103,14 @@ Key hash(const _Item<S>& item) { | |||
|         while(deg > N) { ms++; deg -= N; } | ||||
|         ls += int(deg); | ||||
|         ret.push_back(char(ms)); ret.push_back(char(ls)); | ||||
|         circ += seg.length(); | ||||
|         circ += std::sqrt(seg.template sqlength<double>()); | ||||
|     } | ||||
| 
 | ||||
|     it = ctr.begin(); nx = std::next(it); | ||||
| 
 | ||||
|     while(nx != ctr.end()) { | ||||
|         Segment seg(*it++, *nx++); | ||||
|         auto l = int(M * seg.length() / circ); | ||||
|         auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ); | ||||
|         int ms = 'A', ls = 'A'; | ||||
|         while(l > N) { ms++; l -= N; } | ||||
|         ls += l; | ||||
|  | @ -249,6 +249,11 @@ template<class RawShape> class EdgeCache { | |||
|     std::vector<ContourCache> holes_; | ||||
| 
 | ||||
|     double accuracy_ = 1.0; | ||||
|      | ||||
|     static double length(const Edge &e)  | ||||
|     {  | ||||
|         return std::sqrt(e.template sqlength<double>()); | ||||
|     } | ||||
| 
 | ||||
|     void createCache(const RawShape& sh) { | ||||
|         {   // For the contour
 | ||||
|  | @ -260,7 +265,7 @@ template<class RawShape> class EdgeCache { | |||
| 
 | ||||
|             while(next != endit) { | ||||
|                 contour_.emap.emplace_back(*(first++), *(next++)); | ||||
|                 contour_.full_distance += contour_.emap.back().length(); | ||||
|                 contour_.full_distance += length(contour_.emap.back()); | ||||
|                 contour_.distances.emplace_back(contour_.full_distance); | ||||
|             } | ||||
|         } | ||||
|  | @ -275,7 +280,7 @@ template<class RawShape> class EdgeCache { | |||
| 
 | ||||
|             while(next != endit) { | ||||
|                 hc.emap.emplace_back(*(first++), *(next++)); | ||||
|                 hc.full_distance += hc.emap.back().length(); | ||||
|                 hc.full_distance += length(hc.emap.back()); | ||||
|                 hc.distances.emplace_back(hc.full_distance); | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -311,19 +311,19 @@ struct range_value<bp2d::Shapes> { | |||
| 
 | ||||
| namespace libnest2d { // Now the algorithms that boost can provide...
 | ||||
| 
 | ||||
| namespace pointlike { | ||||
| template<> | ||||
| inline double distance(const PointImpl& p1, const PointImpl& p2 ) | ||||
| { | ||||
|     return boost::geometry::distance(p1, p2); | ||||
| } | ||||
| //namespace pointlike {
 | ||||
| //template<>
 | ||||
| //inline double distance(const PointImpl& p1, const PointImpl& p2 )
 | ||||
| //{
 | ||||
| //    return boost::geometry::distance(p1, p2);
 | ||||
| //}
 | ||||
| 
 | ||||
| template<> | ||||
| inline double distance(const PointImpl& p, const bp2d::Segment& seg ) | ||||
| { | ||||
|     return boost::geometry::distance(p, seg); | ||||
| } | ||||
| } | ||||
| //template<>
 | ||||
| //inline double distance(const PointImpl& p, const bp2d::Segment& seg )
 | ||||
| //{
 | ||||
| //    return boost::geometry::distance(p, seg);
 | ||||
| //}
 | ||||
| //}
 | ||||
| 
 | ||||
| namespace shapelike { | ||||
| // Tell libnest2d how to make string out of a ClipperPolygon object
 | ||||
|  | @ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape) | |||
| } | ||||
| 
 | ||||
| #ifndef DISABLE_BOOST_BOUNDING_BOX | ||||
| template<> | ||||
| inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) | ||||
| { | ||||
|     bp2d::Box b; | ||||
|     boost::geometry::envelope(sh, b); | ||||
|     return b; | ||||
| } | ||||
| 
 | ||||
| template<> | ||||
| inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&) | ||||
| inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&) | ||||
| { | ||||
|     bp2d::Box b; | ||||
|     boost::geometry::envelope(sh, b); | ||||
|  | @ -410,9 +403,9 @@ inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes, | |||
| 
 | ||||
| #ifndef DISABLE_BOOST_CONVEX_HULL | ||||
| template<> | ||||
| inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) | ||||
| inline PathImpl convexHull(const PathImpl& sh, const PathTag&) | ||||
| { | ||||
|     PolygonImpl ret; | ||||
|     PathImpl ret; | ||||
|     boost::geometry::convex_hull(sh, ret); | ||||
|     return ret; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										268
									
								
								src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,268 @@ | |||
| #ifndef ROTCALIPERS_HPP | ||||
| #define ROTCALIPERS_HPP | ||||
| 
 | ||||
| #include <numeric> | ||||
| #include <functional> | ||||
| #include <array> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| template<class Pt, class Unit = TCompute<Pt>> class RotatedBox { | ||||
|     Pt axis_; | ||||
|     Unit bottom_ = Unit(0), right_ = Unit(0); | ||||
| public: | ||||
|      | ||||
|     RotatedBox() = default; | ||||
|     RotatedBox(const Pt& axis, Unit b, Unit r): | ||||
|         axis_(axis), bottom_(b), right_(r) {} | ||||
|      | ||||
|     inline long double area() const {  | ||||
|         long double asq = pl::magnsq<Pt, long double>(axis_); | ||||
|         return cast<long double>(bottom_) * cast<long double>(right_) / asq; | ||||
|     } | ||||
|      | ||||
|     inline long double width() const {  | ||||
|         return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_)); | ||||
|     } | ||||
|      | ||||
|     inline long double height() const {  | ||||
|         return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_)); | ||||
|     } | ||||
|      | ||||
|     inline Unit bottom_extent() const { return bottom_; } | ||||
|     inline Unit right_extent() const { return right_;  } | ||||
|     inline const Pt& axis() const { return axis_; } | ||||
|      | ||||
|     inline Radians angleToX() const { | ||||
|         double ret = std::atan2(getY(axis_), getX(axis_)); | ||||
|         auto s = std::signbit(ret); | ||||
|         if(s) ret += Pi_2; | ||||
|         return -ret; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>  | ||||
| Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0)) | ||||
| { | ||||
|     Poly ret; sl::reserve(ret, sl::contourVertexCount(sh)); | ||||
|      | ||||
|     Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh)); | ||||
|      | ||||
|     auto it  = sl::cbegin(sh); | ||||
|     auto itx = std::next(it); | ||||
|     if(itx != sl::cend(sh)) while (it != sl::cend(sh)) | ||||
|     { | ||||
|         Pt enext = *itx - *it; | ||||
| 
 | ||||
|         auto dp = pl::dotperp<Pt, Unit>(eprev, enext); | ||||
|         if(abs(dp) > eps) sl::addVertex(ret, *it); | ||||
|          | ||||
|         eprev = enext; | ||||
|         if (++itx == sl::cend(sh)) itx = sl::cbegin(sh); | ||||
|         ++it; | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // The area of the bounding rectangle with the axis dir and support vertices
 | ||||
| template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>  | ||||
| inline R rectarea(const Pt& w, // the axis
 | ||||
|                   const Pt& vb, const Pt& vr,  | ||||
|                   const Pt& vt, const Pt& vl)  | ||||
| { | ||||
|     Unit a = pl::dot<Pt, Unit>(w, vr - vl);  | ||||
|     Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb); | ||||
|     R m = R(a) / pl::magnsq<Pt, Unit>(w); | ||||
|     m = m * b; | ||||
|     return m; | ||||
| }; | ||||
| 
 | ||||
| template<class Pt,  | ||||
|          class Unit = TCompute<Pt>, | ||||
|          class R = TCompute<Pt>, | ||||
|          class It = typename std::vector<Pt>::const_iterator> | ||||
| inline R rectarea(const Pt& w, const std::array<It, 4>& rect) | ||||
| { | ||||
|     return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]); | ||||
| } | ||||
| 
 | ||||
| // This function is only applicable to counter-clockwise oriented convex
 | ||||
| // polygons where only two points can be collinear witch each other.
 | ||||
| template <class RawShape,  | ||||
|           class Unit = TCompute<RawShape>,  | ||||
|           class Ratio = TCompute<RawShape>>  | ||||
| RotatedBox<TPoint<RawShape>, Unit> minAreaBoundingBox(const RawShape& sh)  | ||||
| { | ||||
|     using Point = TPoint<RawShape>; | ||||
|     using Iterator = typename TContour<RawShape>::const_iterator; | ||||
|     using pointlike::dot; using pointlike::magnsq; using pointlike::perp; | ||||
| 
 | ||||
|     // Get the first and the last vertex iterator
 | ||||
|     auto first = sl::cbegin(sh); | ||||
|     auto last = std::prev(sl::cend(sh)); | ||||
|      | ||||
|     // Check conditions and return undefined box if input is not sane.
 | ||||
|     if(last == first) return {}; | ||||
|     if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last; | ||||
|     if(last - first < 2) return {}; | ||||
|      | ||||
|     RawShape shcpy; // empty at this point
 | ||||
|     {    | ||||
|         Point p = *first, q = *std::next(first), r = *last; | ||||
|          | ||||
|         // Determine orientation from first 3 vertex (should be consistent)
 | ||||
|         Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - | ||||
|                  (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); | ||||
|          | ||||
|         if(d > 0) {  | ||||
|             // The polygon is clockwise. A flip is needed (for now)
 | ||||
|             sl::reserve(shcpy, last - first); | ||||
|             auto it = last; while(it != first) sl::addVertex(shcpy, *it--); | ||||
|             sl::addVertex(shcpy, *first); | ||||
|             first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Cyclic iterator increment
 | ||||
|     auto inc = [&first, &last](Iterator& it) { | ||||
|        if(it == last) it = first; else ++it; | ||||
|     }; | ||||
|      | ||||
|     // Cyclic previous iterator
 | ||||
|     auto prev = [&first, &last](Iterator it) {  | ||||
|         return it == first ? last : std::prev(it);  | ||||
|     }; | ||||
|      | ||||
|     // Cyclic next iterator
 | ||||
|     auto next = [&first, &last](Iterator it) { | ||||
|         return it == last ? first : std::next(it);     | ||||
|     }; | ||||
|      | ||||
|     // Establish initial (axis aligned) rectangle support verices by determining 
 | ||||
|     // polygon extremes:
 | ||||
|      | ||||
|     auto it = first; | ||||
|     Iterator minX = it, maxX = it, minY = it, maxY = it; | ||||
|      | ||||
|     do { // Linear walk through the vertices and save the extreme positions
 | ||||
|          | ||||
|         Point v = *it, d = v - *minX; | ||||
|         if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it; | ||||
|          | ||||
|         d = v - *maxX; | ||||
|         if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it; | ||||
|          | ||||
|         d = v - *minY; | ||||
|         if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it; | ||||
|          | ||||
|         d = v - *maxY; | ||||
|         if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it; | ||||
|          | ||||
|     } while(++it != std::next(last)); | ||||
|      | ||||
|     // Update the vertices defining the bounding rectangle. The rectangle with
 | ||||
|     // the smallest rotation is selected and the supporting vertices are 
 | ||||
|     // returned in the 'rect' argument.
 | ||||
|     auto update = [&next, &inc] | ||||
|             (const Point& w, std::array<Iterator, 4>& rect)  | ||||
|     { | ||||
|         Iterator B = rect[0], Bn = next(B); | ||||
|         Iterator R = rect[1], Rn = next(R); | ||||
|         Iterator T = rect[2], Tn = next(T); | ||||
|         Iterator L = rect[3], Ln = next(L); | ||||
|          | ||||
|         Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L; | ||||
|         Point pw = perp(w); | ||||
|         using Pt = Point; | ||||
|          | ||||
|         Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r); | ||||
|         Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l); | ||||
|         Unit dw     = magnsq<Pt, Unit>(w); | ||||
|          | ||||
|         std::array<Ratio, 4> angles; | ||||
|         angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb; | ||||
|         angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr; | ||||
|         angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt; | ||||
|         angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl; | ||||
|          | ||||
|         using AngleIndex = std::pair<Ratio, size_t>; | ||||
|         std::vector<AngleIndex> A; A.reserve(4); | ||||
| 
 | ||||
|         for (size_t i = 3, j = 0; j < 4; i = j++) { | ||||
|             if(rect[i] != rect[j] && angles[i] < dw) { | ||||
|                 auto iv = std::make_pair(angles[i], i); | ||||
|                 auto it = std::lower_bound(A.begin(), A.end(), iv, | ||||
|                                            [](const AngleIndex& ai,  | ||||
|                                               const AngleIndex& aj)  | ||||
|                 {  | ||||
|                     return ai.first > aj.first;  | ||||
|                 }); | ||||
|                  | ||||
|                 A.insert(it, iv); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // The polygon is supposed to be a rectangle.
 | ||||
|         if(A.empty()) return false; | ||||
|         | ||||
|         auto amin = A.front().first; | ||||
|         auto imin = A.front().second; | ||||
|         for(auto& a : A) if(a.first == amin) inc(rect[a.second]); | ||||
|              | ||||
|         std::rotate(rect.begin(), rect.begin() + imin, rect.end()); | ||||
|          | ||||
|         return true; | ||||
|     }; | ||||
|      | ||||
|     Point w(1, 0); | ||||
|     Point w_min = w; | ||||
|     Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) *  | ||||
|                   (Unit(getY(*maxY)) - getY(*minY))); | ||||
|      | ||||
|     std::array<Iterator, 4> rect = {minY, maxX, maxY, minX}; | ||||
|     std::array<Iterator, 4> minrect = rect; | ||||
|      | ||||
|     // An edge might be examined twice in which case the algorithm terminates.
 | ||||
|     size_t c = 0, count = last - first + 1; | ||||
|     std::vector<bool> edgemask(count, false); | ||||
|      | ||||
|     while(c++ < count)  | ||||
|     {    | ||||
|         // Update the support vertices, if cannot be updated, break the cycle.
 | ||||
|         if(! update(w, rect)) break; | ||||
|          | ||||
|         size_t eidx = size_t(rect[0] - first); | ||||
|          | ||||
|         if(edgemask[eidx]) break; | ||||
|         edgemask[eidx] = true; | ||||
|                  | ||||
|         // get the unnormalized direction vector
 | ||||
|         w = *rect[0] - *prev(rect[0]); | ||||
|          | ||||
|         // get the area of the rotated rectangle
 | ||||
|         Ratio rarea = rectarea<Point, Unit, Ratio>(w, rect); | ||||
|          | ||||
|         // Update min area and the direction of the min bounding box;
 | ||||
|         if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; } | ||||
|     } | ||||
|      | ||||
|     Unit a = dot<Point, Unit>(w_min, *minrect[1] - *minrect[3]); | ||||
|     Unit b = dot<Point, Unit>(-perp(w_min), *minrect[2] - *minrect[0]); | ||||
|     RotatedBox<Point, Unit> bb(w_min, a, b); | ||||
|      | ||||
|     return bb; | ||||
| } | ||||
| 
 | ||||
| template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh) | ||||
| { | ||||
|     return minAreaBoundingBox(sh).angleToX(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // ROTCALIPERS_HPP
 | ||||
							
								
								
									
										23
									
								
								src/libnest2d/src/libnest2d.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/libnest2d/src/libnest2d.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| #include <libnest2d.h> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| template class Nester<NfpPlacer, FirstFitSelection>; | ||||
| template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||
| 
 | ||||
| template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                         std::vector<Item>::iterator to, | ||||
|                         const Box& bin, | ||||
|                         Coord dist = 0, | ||||
|                         const NfpPlacer::Config& pconf, | ||||
|                         const FirstFitSelection::Config& sconf); | ||||
| 
 | ||||
| template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                         std::vector<Item>::iterator to, | ||||
|                         const Box& bin, | ||||
|                         ProgressFunction prg, | ||||
|                         StopCondition scond, | ||||
|                         Coord dist = 0, | ||||
|                         const NfpPlacer::Config& pconf, | ||||
|                         const FirstFitSelection::Config& sconf); | ||||
| } | ||||
|  | @ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt | |||
| 
 | ||||
| target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) | ||||
| 
 | ||||
| target_include_directories(tests_clipper_nlopt PRIVATE BEFORE | ||||
|     ${GTEST_INCLUDE_DIRS}) | ||||
| target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS}) | ||||
| 
 | ||||
| if(NOT LIBNEST2D_HEADER_ONLY) | ||||
|     target_link_libraries(tests_clipper_nlopt ${LIBNAME}) | ||||
| else() | ||||
|     target_link_libraries(tests_clipper_nlopt libnest2d) | ||||
| endif() | ||||
| 
 | ||||
| add_test(libnest2d_tests tests_clipper_nlopt) | ||||
|  |  | |||
|  | @ -3,11 +3,43 @@ | |||
| 
 | ||||
| #include <libnest2d.h> | ||||
| #include "printer_parts.h" | ||||
| #include <libnest2d/geometry_traits_nfp.hpp> | ||||
| //#include <libnest2d/geometry_traits_nfp.hpp>
 | ||||
| #include "../tools/svgtools.hpp" | ||||
| #include <libnest2d/utils/rotcalipers.hpp> | ||||
| 
 | ||||
| #include "boost/multiprecision/integer.hpp" | ||||
| #include "boost/rational.hpp" | ||||
| 
 | ||||
| //#include "../tools/Int128.hpp"
 | ||||
| 
 | ||||
| //#include "gte/Mathematics/GteMinimumAreaBox2.h"
 | ||||
| 
 | ||||
| //#include "../tools/libnfpglue.hpp"
 | ||||
| //#include "../tools/nfp_svgnest_glue.hpp"
 | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| #if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) | ||||
| using LargeInt = __int128; | ||||
| #else | ||||
| using LargeInt = boost::multiprecision::int128_t; | ||||
| template<> struct _NumTag<LargeInt> { using Type = ScalarTag; }; | ||||
| #endif | ||||
| template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; }; | ||||
| 
 | ||||
| namespace nfp { | ||||
| 
 | ||||
| template<class S> | ||||
| struct NfpImpl<S, NfpLevel::CONVEX_ONLY> | ||||
| { | ||||
|     NfpResult<S> operator()(const S &sh, const S &other) | ||||
|     { | ||||
|         return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| std::vector<libnest2d::Item>& prusaParts() { | ||||
|     static std::vector<libnest2d::Item> ret; | ||||
| 
 | ||||
|  | @ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles) | |||
|     ASSERT_DOUBLE_EQ(rad, Pi); | ||||
|     ASSERT_DOUBLE_EQ(deg, 180); | ||||
|     ASSERT_DOUBLE_EQ(deg2, 180); | ||||
|     ASSERT_DOUBLE_EQ(rad, (Radians) deg); | ||||
|     ASSERT_DOUBLE_EQ( (Degrees) rad, deg); | ||||
|     ASSERT_DOUBLE_EQ(rad, Radians(deg)); | ||||
|     ASSERT_DOUBLE_EQ( Degrees(rad), deg); | ||||
| 
 | ||||
|     ASSERT_TRUE(rad == deg); | ||||
| 
 | ||||
|  | @ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) { | |||
| 
 | ||||
|     Segment seg(p1, p3); | ||||
| 
 | ||||
|     ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); | ||||
| //    ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
 | ||||
| 
 | ||||
|     auto result = pointlike::horizontalDistance(p2, seg); | ||||
| 
 | ||||
|     auto check = [](Coord val, Coord expected) { | ||||
|         if(std::is_floating_point<Coord>::value) | ||||
|     auto check = [](TCompute<Coord> val, TCompute<Coord> expected) { | ||||
|         if(std::is_floating_point<TCompute<Coord>>::value) | ||||
|             ASSERT_DOUBLE_EQ(static_cast<double>(val), | ||||
|                              static_cast<double>(expected)); | ||||
|         else | ||||
|  | @ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | |||
| namespace { | ||||
| using namespace libnest2d; | ||||
| 
 | ||||
| template<unsigned long SCALE = 1, class Bin> | ||||
| template<long long SCALE = 1, class Bin> | ||||
| void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) { | ||||
| 
 | ||||
| 
 | ||||
|  | @ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| TEST(GeometryAlgorithms, convexHull) { | ||||
|     using namespace libnest2d; | ||||
| 
 | ||||
|     ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; | ||||
| 
 | ||||
|     auto chull = sl::convexHull(poly); | ||||
|      | ||||
|     ASSERT_EQ(chull.size(), poly.size()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| TEST(GeometryAlgorithms, NestTest) { | ||||
|     std::vector<Item> input = prusaParts(); | ||||
| 
 | ||||
|     PackGroup result = libnest2d::nest(input, | ||||
|                                        Box(250000000, 210000000), | ||||
|                                        [](unsigned cnt) { | ||||
|                                            std::cout | ||||
|                                                << "parts left: " << cnt | ||||
|                                                << std::endl; | ||||
|                                        }); | ||||
| 
 | ||||
|     ASSERT_LE(result.size(), 2); | ||||
| 
 | ||||
|     int partsum = std::accumulate(result.begin(), | ||||
|                                   result.end(), | ||||
|                                   0, | ||||
|                                   [](int s, | ||||
|                                      const decltype(result)::value_type &bin) { | ||||
|                                       return s += bin.size(); | ||||
|                                   }); | ||||
|      | ||||
|     ASSERT_EQ(input.size(), partsum); | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| struct ItemPair { | ||||
|  | @ -713,7 +780,7 @@ void testNfp(const std::vector<ItemPair>& testdata) { | |||
| 
 | ||||
|     auto& exportfun = exportSVG<SCALE, Box>; | ||||
| 
 | ||||
|     auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ | ||||
|     auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ | ||||
|         testcase++; | ||||
| 
 | ||||
|         orbiter.translate({210*SCALE, 0}); | ||||
|  | @ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { | |||
|     rect2.translate({10, 0}); | ||||
|     rect3.translate({25, 0}); | ||||
| 
 | ||||
|     shapelike::Shapes<PolygonImpl> pile; | ||||
|     TMultiShape<PolygonImpl> pile; | ||||
|     pile.push_back(rect1.transformedShape()); | ||||
|     pile.push_back(rect2.transformedShape()); | ||||
| 
 | ||||
|  | @ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { | |||
|     ASSERT_EQ(shapelike::area(result.front()), ref.area()); | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| long double refMinAreaBox(const PolygonImpl& p) {     | ||||
|      | ||||
|     auto it = sl::cbegin(p), itx = std::next(it); | ||||
|      | ||||
|     long double min_area = std::numeric_limits<long double>::max(); | ||||
|      | ||||
|   | ||||
|     auto update_min = [&min_area, &it, &itx, &p]() { | ||||
|         Segment s(*it, *itx); | ||||
|          | ||||
|         PolygonImpl rotated = p; | ||||
|         sl::rotate(rotated, -s.angleToXaxis()); | ||||
|         auto bb = sl::boundingBox(rotated); | ||||
|         auto area = cast<long double>(sl::area(bb)); | ||||
|         if(min_area > area) min_area = area; | ||||
|     }; | ||||
|      | ||||
|     while(itx != sl::cend(p)) { | ||||
|         update_min(); | ||||
|         ++it; ++itx; | ||||
|     } | ||||
|      | ||||
|     it = std::prev(sl::cend(p)); itx = sl::cbegin(p); | ||||
|     update_min(); | ||||
|      | ||||
|     return min_area; | ||||
| } | ||||
| 
 | ||||
| template<class T> struct BoostGCD {  | ||||
|     T operator()(const T &a, const T &b) { return boost::gcd(a, b); } | ||||
| }; | ||||
| 
 | ||||
| using Unit = int64_t; | ||||
| using Ratio = boost::rational<boost::multiprecision::int128_t>;// Rational<boost::multiprecision::int256_t>;
 | ||||
| 
 | ||||
| //double gteMinAreaBox(const PolygonImpl& p) {    
 | ||||
|      | ||||
| //    using GteCoord = ClipperLib::cInt;
 | ||||
| //    using GtePoint = gte::Vector2<GteCoord>;
 | ||||
|   | ||||
| //    gte::MinimumAreaBox2<GteCoord, Ratio> mb;
 | ||||
|      | ||||
| //    std::vector<GtePoint> points; 
 | ||||
| //    points.reserve(p.Contour.size());
 | ||||
|      | ||||
| //    for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)});
 | ||||
|      | ||||
| //    mb(int(points.size()), points.data(), 0, nullptr, true);
 | ||||
|      | ||||
| //    auto min_area = double(mb.GetArea());
 | ||||
|      | ||||
| //    return min_area;
 | ||||
| //}
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| TEST(RotatingCalipers, MinAreaBBCClk) { | ||||
| //    PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}});
 | ||||
|      | ||||
| //    PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}});
 | ||||
|      | ||||
|     auto u = [](ClipperLib::cInt n) { return n*1000000; }; | ||||
|     PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); | ||||
|      | ||||
|      | ||||
|     long double arearef = refMinAreaBox(poly); | ||||
|     long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area(); | ||||
| //    double gtearea = gteMinAreaBox(poly);
 | ||||
|      | ||||
|     ASSERT_LE(std::abs(area - arearef), 500e6 ); | ||||
| //    ASSERT_LE(std::abs(gtearea - arearef), 500 );
 | ||||
| //    ASSERT_DOUBLE_EQ(gtearea, arearef);
 | ||||
| } | ||||
| 
 | ||||
| TEST(RotatingCalipers, AllPrusaMinBB) { | ||||
|     size_t idx = 0; | ||||
|     long double err_epsilon = 500e6l; | ||||
|      | ||||
|     for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { | ||||
| //        ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
 | ||||
| //        rinput.pop_back();
 | ||||
| //        std::reverse(rinput.begin(), rinput.end());
 | ||||
|          | ||||
| //        PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
 | ||||
|         PolygonImpl poly(rinput); | ||||
|          | ||||
|         long double arearef = refMinAreaBox(poly); | ||||
|         auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput); | ||||
|         long double area = cast<long double>(bb.area()); | ||||
| //        double area = gteMinAreaBox(poly);
 | ||||
|          | ||||
|         bool succ = std::abs(arearef - area) < err_epsilon; | ||||
|         std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "  | ||||
|                   << arearef << " actual: " << area << std::endl; | ||||
|          | ||||
|         ASSERT_TRUE(succ); | ||||
|     } | ||||
|      | ||||
|     for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) { | ||||
|         rinput.pop_back(); | ||||
|         std::reverse(rinput.begin(), rinput.end()); | ||||
|          | ||||
|         PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000)); | ||||
|          | ||||
|          | ||||
|         long double arearef = refMinAreaBox(poly); | ||||
|         auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly); | ||||
|         long double area = cast<long double>(bb.area()); | ||||
| //        double area = gteMinAreaBox(poly);
 | ||||
|          | ||||
|         bool succ = std::abs(arearef - area) < err_epsilon; | ||||
|         std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "  | ||||
|                   << arearef << " actual: " << area << std::endl; | ||||
|          | ||||
|         ASSERT_TRUE(succ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int main(int argc, char **argv) { | ||||
|   ::testing::InitGoogleTest(&argc, argv); | ||||
|   return RUN_ALL_TESTS(); | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -163,6 +160,8 @@ add_library(libslic3r STATIC | |||
|     MTUtils.hpp | ||||
|     Zipper.hpp | ||||
|     Zipper.cpp | ||||
|     MinAreaBoundingBox.hpp | ||||
|     MinAreaBoundingBox.cpp | ||||
|     miniz_extension.hpp | ||||
|     miniz_extension.cpp | ||||
|     SLA/SLABoilerPlate.hpp | ||||
|  | @ -175,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) | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
|  |  | |||
|  | @ -37,6 +37,8 @@ | |||
| *                                                                              * | ||||
| *******************************************************************************/ | ||||
| 
 | ||||
| #ifndef SLIC3R_INT128_HPP | ||||
| #define SLIC3R_INT128_HPP | ||||
| // #define SLIC3R_DEBUG
 | ||||
| 
 | ||||
| // Make assert active if SLIC3R_DEBUG
 | ||||
|  | @ -48,6 +50,8 @@ | |||
| #endif | ||||
| 
 | ||||
| #include <cassert> | ||||
| #include <cstdint> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) | ||||
| 	#define HAS_INTRINSIC_128_TYPE | ||||
|  | @ -293,3 +297,5 @@ public: | |||
| 		return sign_determinant_2x2(p1, q1, p2, q2) * invert; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| #endif // SLIC3R_INT128_HPP
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
							
								
								
									
										142
									
								
								src/libslic3r/MinAreaBoundingBox.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/libslic3r/MinAreaBoundingBox.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,142 @@ | |||
| #include "MinAreaBoundingBox.hpp" | ||||
| 
 | ||||
| #include <libslic3r/ExPolygon.hpp> | ||||
| #include <boost/rational.hpp> | ||||
| 
 | ||||
| #include <libslic3r/Int128.hpp> | ||||
| 
 | ||||
| #if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) | ||||
| #include <boost/multiprecision/integer.hpp> | ||||
| #endif | ||||
| 
 | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| #include <libnest2d/utils/rotcalipers.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| template<> struct PointType<Slic3r::Points>      { using Type = Slic3r::Point; }; | ||||
| template<> struct CoordType<Slic3r::Point>       { using Type = coord_t; }; | ||||
| template<> struct ShapeTag<Slic3r::ExPolygon>    { using Type = PolygonTag; }; | ||||
| template<> struct ShapeTag<Slic3r::Polygon>      { using Type = PolygonTag; }; | ||||
| template<> struct ShapeTag<Slic3r::Points>       { using Type = PathTag; }; | ||||
| template<> struct ShapeTag<Slic3r::Point>        { using Type = PointTag; }; | ||||
| template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Points; }; | ||||
| template<> struct ContourType<Slic3r::Polygon>   { using Type = Slic3r::Points; }; | ||||
| 
 | ||||
| namespace pointlike { | ||||
| 
 | ||||
| template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); } | ||||
| template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); } | ||||
| template<> inline coord_t& x(Slic3r::Point& p)      { return p.x(); } | ||||
| template<> inline coord_t& y(Slic3r::Point& p)      { return p.y(); } | ||||
| 
 | ||||
| } // pointlike
 | ||||
| 
 | ||||
| namespace shapelike { | ||||
| template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; } | ||||
| template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; } | ||||
| template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; } | ||||
| template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } | ||||
| 
 | ||||
| template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} | ||||
| template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); } | ||||
| template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} | ||||
| template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } | ||||
| 
 | ||||
| template<> inline Slic3r::ExPolygon create<Slic3r::ExPolygon>(Slic3r::Points&& contour) | ||||
| { | ||||
|     Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour); | ||||
|     return expoly; | ||||
| } | ||||
| 
 | ||||
| template<> inline Slic3r::Polygon create<Slic3r::Polygon>(Slic3r::Points&& contour) | ||||
| { | ||||
|     Slic3r::Polygon poly; poly.points.swap(contour); | ||||
|     return poly; | ||||
| } | ||||
| 
 | ||||
| } // shapelike
 | ||||
| } // libnest2d
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| // Used as compute type.
 | ||||
| using Unit = int64_t; | ||||
| 
 | ||||
| #if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) | ||||
| using Rational = boost::rational<boost::multiprecision::int128_t>; | ||||
| #else | ||||
| using Rational = boost::rational<__int128>; | ||||
| #endif | ||||
| 
 | ||||
| MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) | ||||
| { | ||||
|     const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); | ||||
|      | ||||
|     libnest2d::RotatedBox<Point, Unit> box =  | ||||
|             libnest2d::minAreaBoundingBox<Polygon, Unit, Rational>(chull); | ||||
|      | ||||
|     m_right = box.right_extent(); | ||||
|     m_bottom = box.bottom_extent(); | ||||
|     m_axis = box.axis(); | ||||
| } | ||||
| 
 | ||||
| MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) | ||||
| { | ||||
|     const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); | ||||
|      | ||||
|     libnest2d::RotatedBox<Point, Unit> box =  | ||||
|             libnest2d::minAreaBoundingBox<ExPolygon, Unit, Rational>(chull); | ||||
|      | ||||
|     m_right = box.right_extent(); | ||||
|     m_bottom = box.bottom_extent(); | ||||
|     m_axis = box.axis(); | ||||
| } | ||||
| 
 | ||||
| MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) | ||||
| { | ||||
|     const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts); | ||||
|      | ||||
|     libnest2d::RotatedBox<Point, Unit> box =  | ||||
|             libnest2d::minAreaBoundingBox<Points, Unit, Rational>(chull); | ||||
|      | ||||
|     m_right = box.right_extent(); | ||||
|     m_bottom = box.bottom_extent(); | ||||
|     m_axis = box.axis(); | ||||
| } | ||||
| 
 | ||||
| double MinAreaBoundigBox::angle_to_X() const | ||||
| { | ||||
|     double ret = std::atan2(m_axis.y(), m_axis.x()); | ||||
|     auto s = std::signbit(ret); | ||||
|     if(s) ret += 2 * PI; | ||||
|     return -ret; | ||||
| } | ||||
| 
 | ||||
| long double MinAreaBoundigBox::width() const | ||||
| { | ||||
|     return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis)); | ||||
| } | ||||
| 
 | ||||
| long double MinAreaBoundigBox::height() const | ||||
| { | ||||
|     return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis)); | ||||
| } | ||||
| 
 | ||||
| long double MinAreaBoundigBox::area() const | ||||
| { | ||||
|     long double asq = libnest2d::pl::magnsq<Point, long double>(m_axis); | ||||
|     return m_bottom * m_right / asq;    | ||||
| } | ||||
| 
 | ||||
| void remove_collinear_points(Polygon &p) | ||||
| { | ||||
|     p = libnest2d::removeCollinearPoints<Polygon>(p, Unit(0)); | ||||
| } | ||||
| 
 | ||||
| void remove_collinear_points(ExPolygon &p) | ||||
| { | ||||
|     p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0)); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										59
									
								
								src/libslic3r/MinAreaBoundingBox.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/libslic3r/MinAreaBoundingBox.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| #ifndef MINAREABOUNDINGBOX_HPP | ||||
| #define MINAREABOUNDINGBOX_HPP | ||||
| 
 | ||||
| #include "libslic3r/Point.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class Polygon; | ||||
| class ExPolygon; | ||||
| 
 | ||||
| void remove_collinear_points(Polygon& p); | ||||
| void remove_collinear_points(ExPolygon& p); | ||||
| 
 | ||||
| /// A class that holds a rotated bounding box. If instantiated with a polygon
 | ||||
| /// type it will hold the minimum area bounding box for the given polygon.
 | ||||
| /// If the input polygon is convex, the complexity is linear to the number of 
 | ||||
| /// points. Otherwise a convex hull of O(n*log(n)) has to be performed.
 | ||||
| class MinAreaBoundigBox { | ||||
|     Point m_axis;     | ||||
|     long double m_bottom = 0.0l, m_right = 0.0l; | ||||
| public: | ||||
|      | ||||
|     // Polygons can be convex or simple (convex or concave with possible holes)
 | ||||
|     enum PolygonLevel { | ||||
|         pcConvex, pcSimple | ||||
|     }; | ||||
|     | ||||
|     // Constructors with various types of geometry data used in Slic3r.
 | ||||
|     // If the convexity is known apriory, pcConvex can be used to skip 
 | ||||
|     // convex hull calculation. It is very important that the input polygons
 | ||||
|     // do NOT have any collinear points (except for the first and the last 
 | ||||
|     // vertex being the same -- meaning a closed polygon for boost)
 | ||||
|     // To make sure this constraint is satisfied, you can call 
 | ||||
|     // remove_collinear_points on the input polygon before handing over here)
 | ||||
|     explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple); | ||||
|     explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple); | ||||
|     explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple); | ||||
|      | ||||
|     // Returns the angle in radians needed for the box to be aligned with the 
 | ||||
|     // X axis. Rotate the polygon by this angle and it will be aligned.
 | ||||
|     double angle_to_X()  const; | ||||
|      | ||||
|     // The box width
 | ||||
|     long double width()  const; | ||||
|      | ||||
|     // The box height
 | ||||
|     long double height() const; | ||||
|      | ||||
|     // The box area
 | ||||
|     long double area()   const; | ||||
|      | ||||
|     // The axis of the rotated box. If the angle_to_X is not sufficiently 
 | ||||
|     // precise, use this unnormalized direction vector.
 | ||||
|     const Point& axis()  const { return m_axis; } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // MINAREABOUNDINGBOX_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); | ||||
|  |  | |||
|  | @ -9,6 +9,31 @@ | |||
| #include <ClipperUtils.hpp> | ||||
| 
 | ||||
| #include <boost/geometry/index/rtree.hpp> | ||||
| #include <boost/multiprecision/integer.hpp> | ||||
| #include <boost/rational.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| #if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) | ||||
| using LargeInt = __int128; | ||||
| #else | ||||
| using LargeInt = boost::multiprecision::int128_t; | ||||
| template<> struct _NumTag<LargeInt> { using Type = ScalarTag; }; | ||||
| #endif | ||||
| template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; }; | ||||
| 
 | ||||
| namespace nfp { | ||||
| 
 | ||||
| template<class S> | ||||
| struct NfpImpl<S, NfpLevel::CONVEX_ONLY> | ||||
| { | ||||
|     NfpResult<S> operator()(const S &sh, const S &other) | ||||
|     { | ||||
|         return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { | |||
| // at the same time, it has to provide reasonable results.
 | ||||
| std::tuple<double /*score*/, Box /*farthest point from bin center*/> | ||||
| objfunc(const PointImpl& bincenter, | ||||
|         const shapelike::Shapes<PolygonImpl>& merged_pile, | ||||
|         const TMultiShape<PolygonImpl>& merged_pile, | ||||
|         const Box& pilebb, | ||||
|         const ItemGroup& items, | ||||
|         const Item &item, | ||||
|  | @ -293,7 +318,7 @@ class AutoArranger {}; | |||
| // management and spatial index structures for acceleration.
 | ||||
| template<class TBin> | ||||
| class _ArrBase { | ||||
| protected: | ||||
| public: | ||||
| 
 | ||||
|     // Useful type shortcuts...
 | ||||
|     using Placer = TPacker<TBin>; | ||||
|  | @ -301,7 +326,9 @@ protected: | |||
|     using Packer = Nester<Placer, Selector>; | ||||
|     using PConfig = typename Packer::PlacementConfig; | ||||
|     using Distance = TCoord<PointImpl>; | ||||
|     using Pile = sl::Shapes<PolygonImpl>; | ||||
|     using Pile = TMultiShape<PolygonImpl>; | ||||
|      | ||||
| protected: | ||||
| 
 | ||||
|     Packer m_pck; | ||||
|     PConfig m_pconf;            // Placement configuration
 | ||||
|  | @ -539,7 +566,10 @@ public: | |||
| // 2D shape from top view.
 | ||||
| using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>; | ||||
| 
 | ||||
| ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) { | ||||
| ShapeData2D projectModelFromTop(const Slic3r::Model &model, | ||||
|                                 const WipeTowerInfo &wti, | ||||
|                                 double               tolerance) | ||||
| { | ||||
|     ShapeData2D ret; | ||||
| 
 | ||||
|     // Count all the items on the bin (all the object's instances)
 | ||||
|  | @ -561,21 +591,32 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& | |||
|             // Object instances should carry the same scaling and
 | ||||
|             // x, y rotation that is why we use the first instance.
 | ||||
|             { | ||||
|                 ModelInstance *finst = objptr->instances.front(); | ||||
|                 Vec3d rotation = finst->get_rotation(); | ||||
|                 rotation.z() = 0.; | ||||
|                 Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror()); | ||||
|                 ModelInstance *finst       = objptr->instances.front(); | ||||
|                 Vec3d          rotation    = finst->get_rotation(); | ||||
|                 rotation.z()               = 0.; | ||||
|                 Transform3d trafo_instance = Geometry::assemble_transform( | ||||
|                     Vec3d::Zero(), | ||||
|                     rotation, | ||||
|                     finst->get_scaling_factor(), | ||||
|                     finst->get_mirror()); | ||||
|                 Polygon p = objptr->convex_hull_2d(trafo_instance); | ||||
| 				assert(! p.points.empty()); | ||||
| 
 | ||||
|                 // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
 | ||||
|                 if (p.points.empty()) | ||||
|                     continue; | ||||
|                  | ||||
|                 assert(!p.points.empty()); | ||||
| 
 | ||||
|                 // this may happen for malformed models, see:
 | ||||
|                 // https://github.com/prusa3d/PrusaSlicer/issues/2209
 | ||||
|                 if (p.points.empty()) continue; | ||||
|                  | ||||
|                 if(tolerance > EPSILON) { | ||||
|                     Polygons pp { p }; | ||||
|                     pp = p.simplify(double(scaled(tolerance))); | ||||
|                     if (!pp.empty()) p = pp.front(); | ||||
|                 } | ||||
|                  | ||||
|                 p.reverse(); | ||||
|                 assert(!p.is_counter_clockwise()); | ||||
|                 p.append(p.first_point()); | ||||
|                 clpath = Slic3rMultiPoint_to_ClipperPath(p); | ||||
|                 auto firstp = clpath.front(); clpath.emplace_back(firstp); | ||||
|             } | ||||
| 
 | ||||
|             Vec3d rotation0 = objptr->instances.front()->get_rotation(); | ||||
|  | @ -589,7 +630,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& | |||
| 
 | ||||
|                 // Invalid geometries would throw exceptions when arranging
 | ||||
|                 if(item.vertexCount() > 3) { | ||||
|                     item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))), | ||||
|                     item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); | ||||
|                     item.translation({ | ||||
|                     ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), | ||||
|                     ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) | ||||
|  | @ -741,6 +782,8 @@ BedShapeHint bedShape(const Polyline &bed) { | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; | ||||
| 
 | ||||
| // The final client function to arrange the Model. A progress indicator and
 | ||||
| // a stop predicate can be also be passed to control the process.
 | ||||
| bool arrange(Model &model,              // The model with the geometries
 | ||||
|  | @ -755,9 +798,9 @@ bool arrange(Model &model,              // The model with the geometries | |||
|              std::function<bool ()> stopcondition) | ||||
| { | ||||
|     bool ret = true; | ||||
| 
 | ||||
|      | ||||
|     // Get the 2D projected shapes with their 3D model instance pointers
 | ||||
|     auto shapemap = arr::projectModelFromTop(model, wti); | ||||
|     auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); | ||||
| 
 | ||||
|     // Copy the references for the shapes only as the arranger expects a
 | ||||
|     // sequence of objects convertible to Item or ClipperPolygon
 | ||||
|  | @ -782,7 +825,7 @@ bool arrange(Model &model,              // The model with the geometries | |||
|                          static_cast<libnest2d::Coord>(bbb.min(0)), | ||||
|                          static_cast<libnest2d::Coord>(bbb.min(1)) | ||||
|                      }, | ||||
|     { | ||||
|                      { | ||||
|                          static_cast<libnest2d::Coord>(bbb.max(0)), | ||||
|                          static_cast<libnest2d::Coord>(bbb.max(1)) | ||||
|                      }); | ||||
|  | @ -856,9 +899,9 @@ void find_new_position(const Model &model, | |||
|                        coord_t min_obj_distance, | ||||
|                        const Polyline &bed, | ||||
|                        WipeTowerInfo& wti) | ||||
| { | ||||
| {     | ||||
|     // Get the 2D projected shapes with their 3D model instance pointers
 | ||||
|     auto shapemap = arr::projectModelFromTop(model, wti); | ||||
|     auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); | ||||
| 
 | ||||
|     // Copy the references for the shapes only as the arranger expects a
 | ||||
|     // sequence of objects convertible to Item or ClipperPolygon
 | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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; | ||||
|     }; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -747,8 +747,8 @@ 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], float(clpr_offs)); | ||||
|                 po.m_model_slices[id] =  | ||||
|                         offset_ex(po.m_model_slices[id], float(clpr_offs)); | ||||
|              | ||||
|             mit->set_model_slice_idx(po, id); ++mit; | ||||
|         } | ||||
|  | @ -1014,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) | ||||
|  | @ -1072,9 +1072,9 @@ void SLAPrint::process() | |||
| 
 | ||||
|         // 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()); | ||||
|  | @ -1088,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); | ||||
|  | @ -1123,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)); | ||||
|                 } | ||||
|  | @ -1295,35 +1295,11 @@ void SLAPrint::process() | |||
|     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
 | ||||
|  | @ -1511,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" | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 { | ||||
| 
 | ||||
|  | @ -326,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. | ||||
|  * | ||||
|  | @ -389,11 +358,10 @@ public: | |||
|     // 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; } | ||||
|  | @ -454,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: | ||||
|  |  | |||
|  | @ -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())); | ||||
|  |  | |||
|  | @ -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()); | ||||
|  |  | |||
|  | @ -5524,7 +5524,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 
 | ||||
|  |  | |||
|  | @ -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 }, | ||||
|  | @ -1597,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") ? | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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> | ||||
|  | @ -37,7 +39,12 @@ | |||
| #include "libslic3r/SLA/SLARotfinder.hpp" | ||||
| #include "libslic3r/Utils.hpp" | ||||
| 
 | ||||
| #include "libnest2d/optimizers/nlopt/genetic.hpp" | ||||
| //#include "libslic3r/ClipperUtils.hpp"
 | ||||
| 
 | ||||
| // #include "libnest2d/optimizers/nlopt/genetic.hpp"
 | ||||
| // #include "libnest2d/backends/clipper/geometries.hpp"
 | ||||
| // #include "libnest2d/utils/rotcalipers.hpp"
 | ||||
| #include "libslic3r/MinAreaBoundingBox.hpp" | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
|  | @ -1253,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; | ||||
| 
 | ||||
|  | @ -1429,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); | ||||
|  | @ -1536,7 +1776,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
| 
 | ||||
| void Plater::priv::update(bool force_full_scene_refresh) | ||||
| { | ||||
|     wxWindowUpdateLocker freeze_guard(q); | ||||
|     // the following line, when enabled, causes flickering on NVIDIA graphics cards
 | ||||
| //    wxWindowUpdateLocker freeze_guard(q);
 | ||||
|     if (get_config("autocenter") == "1") { | ||||
|         // auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
 | ||||
|         // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
 | ||||
|  | @ -1606,7 +1847,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 | ||||
|  | @ -2143,59 +2384,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(); | ||||
| 
 | ||||
|  | @ -2211,129 +2438,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
 | ||||
|     double offs = mindist / 2.0 - EPSILON; | ||||
| 
 | ||||
|     if(rotoptimizing) // wasn't canceled
 | ||||
|     for(ModelInstance * oi : o->instances) { | ||||
|         oi->set_rotation({r[X], r[Y], r[Z]}); | ||||
| 
 | ||||
|         auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); | ||||
| 
 | ||||
|         namespace opt = libnest2d::opt; | ||||
|         opt::StopCriteria stopcr; | ||||
|         stopcr.relative_score_difference = 0.01; | ||||
|         stopcr.max_iterations = 10000; | ||||
|         stopcr.stop_score = 0.0; | ||||
|         opt::GeneticOptimizer solver(stopcr); | ||||
|         Polygon pbed(bed); | ||||
| 
 | ||||
|         auto bin = pbed.bounding_box(); | ||||
|         double binw = bin.size()(X) * SCALING_FACTOR - offs; | ||||
|         double binh = bin.size()(Y) * SCALING_FACTOR - offs; | ||||
| 
 | ||||
|         auto result = solver.optimize_min([&trchull, binw, binh](double rot){ | ||||
|             auto chull = trchull; | ||||
|             chull.rotate(rot); | ||||
| 
 | ||||
|             auto bb = chull.bounding_box(); | ||||
|             double bbw = bb.size()(X) * SCALING_FACTOR; | ||||
|             double bbh = bb.size()(Y) * SCALING_FACTOR; | ||||
| 
 | ||||
|             auto wdiff = bbw - binw; | ||||
|             auto hdiff = bbh - binh; | ||||
|             double diff = 0; | ||||
|             if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; | ||||
|             if(wdiff > 0) diff += wdiff; | ||||
|             if(hdiff > 0) diff += hdiff; | ||||
| 
 | ||||
|             return diff; | ||||
|         }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); | ||||
| 
 | ||||
|         double r = std::get<0>(result.optimum); | ||||
| 
 | ||||
|         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() | ||||
|  | @ -2514,7 +2699,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; | ||||
|     } | ||||
|  | @ -2745,7 +2930,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; | ||||
|         } | ||||
|  | @ -3226,7 +3411,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; | ||||
|     } | ||||
| 
 | ||||
|  | @ -3236,7 +3421,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; | ||||
|     } | ||||
| 
 | ||||
|  | @ -3256,7 +3441,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 | ||||
|  | @ -3323,6 +3508,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)); | ||||
| } | ||||
| 
 | ||||
|  | @ -3383,6 +3569,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); } | ||||
|  | @ -3583,7 +3771,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>()); | ||||
|         } | ||||
|  | @ -3689,7 +3877,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)) { | ||||
|  | @ -3705,6 +3893,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); | ||||
|  | @ -3740,7 +3931,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(); | ||||
|  |  | |||
|  | @ -52,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(); | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2087,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")), "" }; | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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
	
	 YuSanka
						YuSanka