or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-access.mdfile-operations.mdindex.mdmesh-analysis.mdmesh-processing.mdtransformations.md

mesh-processing.mddocs/

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