bevyengine/bevy · error · MeshMergeDuplicateVerticesError
Index attribute already set.
Error message
Index attribute already set.
What it means
MeshMergeDuplicateVerticesError::IndicesAlreadySet (crates/bevy_mesh/src/mesh.rs:3016), returned by Mesh::merge_duplicate_vertices when self.try_indices() finds an existing index buffer. The function's contract is to CREATE indices from a non-indexed vertex soup; an already-indexed mesh violates that precondition, so it refuses instead of overwriting valid topology.
Solutions
- If you really want to rebuild indices, drop the existing buffer first: mesh.remove_indices(), then merge_duplicate_vertices()
- For flat shading of indexed geometry use the documented recipe: mesh.duplicate_vertices() then mesh.compute_flat_normals()
- For smooth shading keep the existing indices and call compute_smooth_normals() instead
Example fix
// before: Sphere builder already inserted indices let mut mesh = Sphere::new(1.0).mesh().build(); mesh.merge_duplicate_vertices(); // Err(IndicesAlreadySet) // after: drop old indices, then dedupe let mut mesh = Sphere::new(1.0).mesh().build(); mesh.remove_indices(); mesh.merge_duplicate_vertices().unwrap();
Defensive patterns
Strategy: try-catch
Validate before calling
if mesh.indices().is_none() {
mesh.merge_duplicate_vertices()?;
} else {
// already indexed: nothing to dedupe, or remove_indices() first if rebuilding
} Type guard
fn is_unindexed(mesh: &Mesh) -> bool {
mesh.indices().is_none()
} Try / catch
match mesh.merge_duplicate_vertices() {
Ok(()) => {}
Err(MeshMergeDuplicateVerticesError::IndicesAlreadySet) => {
// decide: keep existing topology, or mesh.remove_indices() and retry
}
Err(MeshMergeDuplicateVerticesError::MeshAccessError(e)) => {
bevy::log::error!("mesh data unavailable: {e}");
}
} Prevention
- Remember merge_duplicate_vertices is for NON-indexed meshes only
- Gate the call behind mesh.indices().is_none()
- Keep the documented recipes straight: smooth normals need indices, flat normals need duplicate_vertices
When it happens
Trigger: Calling merge_duplicate_vertices() on any mesh that already has indices set: meshes from Bevy primitive builders (Sphere, Torus, ... all insert Indices::U32/U16), imported glTF geometry (indexed by nature), or a mesh where you previously called insert_indices/with_inserted_indices or an earlier successful merge_duplicate_vertices.
Common situations: Developer welds an imported indexed mesh to smooth seams and calls merge_duplicate_vertices directly; mixing up the documented normal-generation recipes (compute_smooth_normals wants indexed geometry, compute_flat_normals wants non-indexed) and reaching for the wrong helper.
Related errors
- Face index data references vertices that do not exist
- Indices weren't in chunks according to topology
- Mesh access error
- missing indices
- The number of vertices exceeds u32::MAX
AI-assisted analysis of bevyengine/bevy@8d743eb7dc (2026-08-20).
Data as JSON: /api/errors/26fd61a39108fc65.
Report an issue: GitHub.
Appendix: source
Thrown at crates/bevy_mesh/src/mesh.rs:3096
.into_iter()
.map(|(id, data)| {
(
id,
SerializedMeshAttributeData::from_mesh_attribute_data(data),
)
})
.collect(),
indices: mesh.indices.replace(None).expect(MESH_EXTRACTED_ERROR),
}
}
/// Create a [`Mesh`] from a [`SerializedMesh`]. See the documentation for [`SerializedMesh`] for caveats.
///
/// Use [`MeshDeserializer`] if you need to pass extra options to the deserialization process, such as specifying custom vertex attributes.
pub fn into_mesh(self) -> Mesh {
MeshDeserializer::default().deserialize(self)
}
}
/// Use to specify extra options when deserializing a [`SerializedMesh`] into a [`Mesh`].
#[cfg(feature = "serialize")]
pub struct MeshDeserializer {
custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
}
#[cfg(feature = "serialize")]
impl Default for MeshDeserializer {
fn default() -> Self {
// Written like this so that the compiler can validate that we use all the built-in attributes.
// If you just added a new attribute and got a compile error, please add it to this list :)
const BUILTINS: [MeshVertexAttribute; Mesh::FIRST_AVAILABLE_CUSTOM_ATTRIBUTE as usize] = [
Mesh::ATTRIBUTE_POSITION,
Mesh::ATTRIBUTE_NORMAL,
Mesh::ATTRIBUTE_UV_0,
Mesh::ATTRIBUTE_UV_1,
Mesh::ATTRIBUTE_TANGENT,View on GitHub (pinned to 8d743eb7dc)