0
# Mesh Processing
1
2
Clean and process mesh data including duplicate removal, empty area elimination, and normal vector calculations for mesh optimization and repair.
3
4
## Capabilities
5
6
### Normal Vector Processing
7
8
Calculate and manage triangle normal vectors for accurate geometric operations.
9
10
```python { .api }
11
def update_normals(self, update_areas=True, update_centroids=True):
12
"""
13
Calculate normal vectors for all triangles.
14
15
Parameters:
16
- update_areas (bool): Whether to calculate triangle surface areas
17
- update_centroids (bool): Whether to calculate triangle centroids
18
19
Notes:
20
- Normals calculated using cross product: (v1-v0) × (v2-v0)
21
- Updates the normals property with computed vectors
22
- Optionally updates dependent geometric properties
23
- Essential for accurate volume and surface area calculations
24
"""
25
26
def get_unit_normals(self):
27
"""
28
Get normalized normal vectors without modifying originals.
29
30
Returns:
31
numpy.array: Unit normal vectors (N, 3)
32
33
Notes:
34
- Returns normalized versions of current normal vectors
35
- Zero-length normals (degenerate triangles) remain unchanged
36
- Does not modify the mesh's normals property
37
- Useful for orientation and lighting calculations
38
"""
39
40
def update_units(self):
41
"""
42
Calculate unit normal vectors and store in units property.
43
44
Notes:
45
- Computes normalized normals scaled by triangle areas
46
- Used internally for surface area and geometric calculations
47
- Results stored in units property for later access
48
"""
49
```
50
51
### Duplicate Triangle Removal
52
53
Remove duplicate triangles to clean mesh topology and reduce file size.
54
55
```python { .api }
56
@classmethod
57
def remove_duplicate_polygons(cls, data, value=RemoveDuplicates.SINGLE):
58
"""
59
Remove duplicate triangles from mesh data.
60
61
Parameters:
62
- data (numpy.array): Input mesh data array
63
- value (RemoveDuplicates): Duplicate handling strategy
64
- NONE: Keep all triangles (no removal)
65
- SINGLE: Keep only one copy of each unique triangle
66
- ALL: Remove all duplicates including originals (creates holes)
67
68
Returns:
69
numpy.array: Processed mesh data with duplicates handled
70
71
Notes:
72
- Identifies duplicates by summing triangle vertex coordinates
73
- Uses lexicographic sorting for efficient duplicate detection
74
- SINGLE mode is most commonly used for mesh cleaning
75
- ALL mode may create holes in the mesh surface
76
"""
77
```
78
79
### Empty Area Triangle Removal
80
81
Remove triangles with zero or near-zero surface area to prevent calculation errors.
82
83
```python { .api }
84
@classmethod
85
def remove_empty_areas(cls, data):
86
"""
87
Remove triangles with zero or negligible surface area.
88
89
Parameters:
90
- data (numpy.array): Input mesh data array
91
92
Returns:
93
numpy.array: Mesh data with empty triangles removed
94
95
Notes:
96
- Calculates triangle areas using cross product magnitudes
97
- Removes triangles smaller than AREA_SIZE_THRESHOLD (typically 0)
98
- Prevents division by zero in normal calculations
99
- Essential for robust geometric computations
100
"""
101
```
102
103
### Mesh Construction Options
104
105
Control mesh processing during construction for clean initialization.
106
107
```python { .api }
108
def __init__(
109
self,
110
data,
111
calculate_normals=True,
112
remove_empty_areas=False,
113
remove_duplicate_polygons=RemoveDuplicates.NONE,
114
name='',
115
speedups=True,
116
**kwargs
117
):
118
"""
119
Initialize mesh with optional processing steps.
120
121
Parameters:
122
- data (numpy.array): Triangle data array
123
- calculate_normals (bool): Whether to calculate normal vectors
124
- remove_empty_areas (bool): Whether to remove zero-area triangles
125
- remove_duplicate_polygons (RemoveDuplicates): Duplicate handling
126
- name (str): Mesh name for identification
127
- speedups (bool): Whether to use Cython optimizations
128
- **kwargs: Additional arguments for base class
129
130
Notes:
131
- Processing steps applied in order: empty areas, duplicates, normals
132
- Processing during construction is more efficient than post-processing
133
- Clean meshes improve accuracy of all subsequent calculations
134
"""
135
```
136
137
## Usage Examples
138
139
### Normal Vector Management
140
141
```python
142
import numpy as np
143
from stl import mesh
144
145
# Load a mesh
146
my_mesh = mesh.Mesh.from_file('model.stl')
147
148
# Recalculate normals (e.g., after vertex modification)
149
my_mesh.update_normals()
150
151
# Get unit normals for lighting calculations
152
unit_normals = my_mesh.get_unit_normals()
153
154
# Check for degenerate triangles
155
normal_lengths = np.linalg.norm(my_mesh.normals, axis=1)
156
degenerate_count = np.sum(normal_lengths < 1e-6)
157
if degenerate_count > 0:
158
print(f"Warning: {degenerate_count} degenerate triangles found")
159
160
# Update only normals without recalculating areas
161
my_mesh.update_normals(update_areas=False, update_centroids=False)
162
```
163
164
### Duplicate Triangle Removal
165
166
```python
167
import numpy as np
168
from stl import mesh
169
170
# Create mesh with duplicate removal during construction
171
raw_data = np.zeros(1000, dtype=mesh.Mesh.dtype)
172
# ... populate raw_data ...
173
174
clean_mesh = mesh.Mesh(
175
raw_data,
176
remove_duplicate_polygons=mesh.RemoveDuplicates.SINGLE
177
)
178
179
# Or clean existing mesh data
180
duplicate_data = my_mesh.data.copy()
181
cleaned_data = mesh.Mesh.remove_duplicate_polygons(
182
duplicate_data,
183
mesh.RemoveDuplicates.SINGLE
184
)
185
clean_mesh = mesh.Mesh(cleaned_data)
186
187
print(f"Original triangles: {len(duplicate_data)}")
188
print(f"After cleanup: {len(cleaned_data)}")
189
```
190
191
### Empty Area Triangle Removal
192
193
```python
194
import numpy as np
195
from stl import mesh
196
197
# Remove empty triangles during construction
198
my_mesh = mesh.Mesh.from_file('model.stl', remove_empty_areas=True)
199
200
# Or clean existing mesh data
201
original_data = my_mesh.data.copy()
202
cleaned_data = mesh.Mesh.remove_empty_areas(original_data)
203
cleaned_mesh = mesh.Mesh(cleaned_data)
204
205
print(f"Removed {len(original_data) - len(cleaned_data)} empty triangles")
206
```
207
208
### Comprehensive Mesh Cleaning
209
210
```python
211
import numpy as np
212
from stl import mesh
213
214
# Load and clean mesh in one step
215
clean_mesh = mesh.Mesh.from_file(
216
'noisy_model.stl',
217
calculate_normals=True,
218
remove_empty_areas=True,
219
remove_duplicate_polygons=mesh.RemoveDuplicates.SINGLE
220
)
221
222
# Manual multi-step cleaning
223
raw_mesh = mesh.Mesh.from_file('noisy_model.stl', calculate_normals=False)
224
print(f"Original triangle count: {len(raw_mesh)}")
225
226
# Step 1: Remove empty areas
227
step1_data = mesh.Mesh.remove_empty_areas(raw_mesh.data)
228
print(f"After removing empty areas: {len(step1_data)}")
229
230
# Step 2: Remove duplicates
231
step2_data = mesh.Mesh.remove_duplicate_polygons(
232
step1_data,
233
mesh.RemoveDuplicates.SINGLE
234
)
235
print(f"After removing duplicates: {len(step2_data)}")
236
237
# Step 3: Create final clean mesh
238
final_mesh = mesh.Mesh(step2_data, calculate_normals=True)
239
print(f"Final clean mesh: {len(final_mesh)} triangles")
240
```
241
242
### Normal Vector Analysis
243
244
```python
245
import numpy as np
246
from stl import mesh
247
248
my_mesh = mesh.Mesh.from_file('model.stl')
249
250
# Analyze normal vector quality
251
normals = my_mesh.normals
252
normal_lengths = np.linalg.norm(normals, axis=1)
253
254
# Find problematic triangles
255
zero_normals = normal_lengths < 1e-10
256
short_normals = (normal_lengths > 1e-10) & (normal_lengths < 1e-3)
257
258
print(f"Zero-length normals: {np.sum(zero_normals)}")
259
print(f"Very short normals: {np.sum(short_normals)}")
260
261
# Get consistent unit normals
262
unit_normals = my_mesh.get_unit_normals()
263
unit_lengths = np.linalg.norm(unit_normals, axis=1)
264
print(f"Unit normal length range: {np.min(unit_lengths):.6f} to {np.max(unit_lengths):.6f}")
265
266
# Check for flipped normals (if mesh should be outward-facing)
267
if my_mesh.is_closed():
268
# For closed meshes, normals should generally point outward
269
# This is a simplified check - more sophisticated methods exist
270
centroid = np.mean(my_mesh.vectors.reshape(-1, 3), axis=0)
271
triangle_centers = np.mean(my_mesh.vectors, axis=1)
272
to_center = centroid - triangle_centers
273
dot_products = np.sum(unit_normals * to_center, axis=1)
274
inward_normals = np.sum(dot_products > 0)
275
print(f"Potentially inward-facing normals: {inward_normals}")
276
```
277
278
### Performance Optimization
279
280
```python
281
import numpy as np
282
from stl import mesh
283
284
# For large meshes, use speedups and batch processing
285
large_mesh = mesh.Mesh.from_file(
286
'large_model.stl',
287
speedups=True, # Use Cython extensions
288
remove_empty_areas=True,
289
remove_duplicate_polygons=mesh.RemoveDuplicates.SINGLE
290
)
291
292
# Batch normal updates for modified meshes
293
def batch_modify_vertices(mesh_obj, modifications):
294
"""Apply multiple vertex modifications efficiently."""
295
# Apply all modifications
296
for triangle_idx, vertex_idx, new_position in modifications:
297
mesh_obj.vectors[triangle_idx, vertex_idx] = new_position
298
299
# Single normal update for all changes
300
mesh_obj.update_normals()
301
302
# Example usage
303
modifications = [
304
(0, 0, [1, 2, 3]), # Triangle 0, vertex 0
305
(0, 1, [4, 5, 6]), # Triangle 0, vertex 1
306
(1, 2, [7, 8, 9]), # Triangle 1, vertex 2
307
]
308
batch_modify_vertices(large_mesh, modifications)
309
```
310
311
### Mesh Validation Pipeline
312
313
```python
314
import numpy as np
315
from stl import mesh
316
317
def validate_and_clean_mesh(filename):
318
"""Complete mesh validation and cleaning pipeline."""
319
320
# Load with minimal processing
321
raw_mesh = mesh.Mesh.from_file(filename, calculate_normals=False)
322
print(f"Loaded mesh: {len(raw_mesh)} triangles")
323
324
# Step 1: Remove degenerate triangles
325
cleaned_data = mesh.Mesh.remove_empty_areas(raw_mesh.data)
326
removed_empty = len(raw_mesh.data) - len(cleaned_data)
327
print(f"Removed {removed_empty} empty triangles")
328
329
# Step 2: Remove duplicates
330
deduped_data = mesh.Mesh.remove_duplicate_polygons(
331
cleaned_data,
332
mesh.RemoveDuplicates.SINGLE
333
)
334
removed_dupes = len(cleaned_data) - len(deduped_data)
335
print(f"Removed {removed_dupes} duplicate triangles")
336
337
# Step 3: Create final mesh with normals
338
final_mesh = mesh.Mesh(deduped_data, calculate_normals=True)
339
340
# Step 4: Validate results
341
normal_lengths = np.linalg.norm(final_mesh.normals, axis=1)
342
bad_normals = np.sum(normal_lengths < 1e-6)
343
if bad_normals > 0:
344
print(f"Warning: {bad_normals} triangles still have degenerate normals")
345
346
print(f"Final mesh: {len(final_mesh)} clean triangles")
347
return final_mesh
348
349
# Usage
350
clean_mesh = validate_and_clean_mesh('input_model.stl')
351
```
352
353
## Algorithm Details
354
355
### Duplicate Detection
356
357
- Uses coordinate sum comparison for efficiency
358
- Lexicographic sorting enables O(n log n) duplicate detection
359
- Floating-point precision may affect duplicate identification
360
361
### Normal Calculation
362
363
- Cross product formula: **n** = (**v1** - **v0**) × (**v2** - **v0**)
364
- Right-hand rule determines normal direction
365
- Zero-area triangles produce zero-length normals
366
367
### Area Threshold
368
369
```python
370
AREA_SIZE_THRESHOLD = 0 # Minimum triangle area (typically 0)
371
```
372
373
## Error Handling
374
375
- **ValueError**: Raised for invalid RemoveDuplicates enum values
376
- **RuntimeError**: May occur during normal calculations with degenerate data
377
- **NumPy warnings**: Generated for numerical issues in geometric calculations