Reject invalid brush element edits
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run

This commit is contained in:
Rbanh 2026-06-06 06:25:43 -04:00
parent 11b0df2b5f
commit d9e52d753b
2 changed files with 50 additions and 17 deletions

View File

@ -2,7 +2,10 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts}; use bevy_egui::{egui, EguiContexts};
use shared::{BrushDesc, BrushFaceDesc, BrushPlaneDesc, ComponentInstanceId}; use shared::{
brush_math::{validate_brush, BrushDiagnosticSeverity},
BrushDesc, BrushFaceDesc, BrushPlaneDesc, ComponentInstanceId,
};
use crate::camera::EditorCamera; use crate::camera::EditorCamera;
use crate::history::{push_command, EditorCommand}; use crate::history::{push_command, EditorCommand};
@ -297,23 +300,41 @@ fn brush_element_drag(
if buttons.just_released(MouseButton::Left) { if buttons.just_released(MouseButton::Left) {
if let (Some(brush), Some(old)) = (drag.brush, drag.old.take()) { if let (Some(brush), Some(old)) = (drag.brush, drag.old.take()) {
if drag.changed { if drag.changed {
if let Ok((brush_desc, _)) = brushes.get(brush) { if let Ok((mut brush_desc, _)) = brushes.get_mut(brush) {
let new = brush_desc.clone(); let new = brush_desc.clone();
commands.queue(move |world: &mut World| { let validation = validate_brush(&new);
push_command( if validation.is_valid() {
world, commands.queue(move |world: &mut World| {
EditorCommand::SetBrush { push_command(
entity: brush, world,
old: Some(old), EditorCommand::SetBrush {
new, entity: brush,
}, old: Some(old),
new,
},
);
});
set_brush_edit_status(
&mut active_operator,
OperatorPhase::Committed,
"Committed brush element edit",
); );
}); } else {
set_brush_edit_status( *brush_desc = old;
&mut active_operator, let message = validation
OperatorPhase::Committed, .diagnostics
"Committed brush element edit", .iter()
); .find(|diagnostic| {
diagnostic.severity == BrushDiagnosticSeverity::Error
})
.map(|diagnostic| diagnostic.message.as_str())
.unwrap_or("Brush edit produced invalid geometry");
set_brush_edit_status(
&mut active_operator,
OperatorPhase::Blocked,
format!("Rejected brush edit: {message}"),
);
}
} }
} }
} }
@ -783,4 +804,16 @@ mod tests {
assert!(changed); assert!(changed);
assert_eq!(before, after); assert_eq!(before, after);
} }
#[test]
fn collapsed_face_move_produces_invalid_brush() {
let mut brush = BrushDesc::default();
let face = brush.faces[0].id.clone();
assert!(move_selected_elements(
&mut brush,
&[BrushElementKey::Face { face }],
Vec3::new(-1.0, 0.0, 0.0),
));
assert!(!validate_brush(&brush).is_valid());
}
} }

View File

@ -17,7 +17,7 @@ Brushes are persisted blockout geometry stored as `ActorKind::Brush + BrushDesc`
With a brush actor selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip modes. Element modes show viewport overlays for the selected brush, route LMB to element picking, support Shift+LMB multi-select, and display a `Brush: <mode>` badge. `Esc` returns to object mode. With a brush actor selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip modes. Element modes show viewport overlays for the selected brush, route LMB to element picking, support Shift+LMB multi-select, and display a `Brush: <mode>` badge. `Esc` returns to object mode.
Vertex, edge, and face selections can be dragged on the viewport floor plane. The editor moves matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, and commits one undoable brush edit when the drag releases. Clip mode currently provides element visualization/selection only; split application is future CSG work. Vertex, edge, and face selections can be dragged on the viewport floor plane. The editor moves matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, validates the result, and commits one undoable brush edit when the drag releases. Invalid drag results are rejected with operator status text and reverted to the pre-drag brush. Clip mode currently provides element visualization/selection only; split application is future CSG work.
When one or more faces are selected in Face mode, the Brush inspector shows selected-face controls for UV offset, UV scale, UV rotation, material ref, and texture ref. Material and texture rows use Asset Browser-aware picker controls with clear, locate, browse, and compatible drag/drop assignment. These fields edit the persisted `BrushFaceDesc` data through undoable brush updates. Per-face material batching in hydration is still follow-up work. When one or more faces are selected in Face mode, the Brush inspector shows selected-face controls for UV offset, UV scale, UV rotation, material ref, and texture ref. Material and texture rows use Asset Browser-aware picker controls with clear, locate, browse, and compatible drag/drop assignment. These fields edit the persisted `BrushFaceDesc` data through undoable brush updates. Per-face material batching in hydration is still follow-up work.