Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

app: extrude status badge & diagnostics panel

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jun 13, 2026, 11:16 PM +0300) commit 0792e960 parent 3ffb7e21 change-id trkozmkt
+492 -22
+20 -7
crates/bone-app/src/main.rs
··· 5 5 6 6 use bone_document::{ 7 7 DimensionKind, DimensionValue, Document, DocumentFolder, EditOutcome, EvaluatedExtrude, 8 - ExtrudeFeature, FeatureCache, FeatureNode, LineData, Sketch, SketchDimension, SketchEdit, 9 - SketchEntity, SketchRelation, SketchVersion, SolverError, UndoStack, 8 + ExtrudeError, ExtrudeFeature, FeatureCache, FeatureNode, LineData, Sketch, SketchDimension, 9 + SketchEdit, SketchEntity, SketchRelation, SketchVersion, SolverError, UndoStack, 10 10 }; 11 11 use bone_render::{ 12 12 Camera2, CameraTween, ChromeInstance, ChromePipeline, ChromeTextPipeline, ConvexInstance, ··· 77 77 PendingDimension, Plane, SketchTool, 78 78 }; 79 79 use snap::{Anchor, SnapHit}; 80 + use status_badge::ExtrudeStatus; 80 81 81 82 #[derive(Debug, thiserror::Error)] 82 83 enum AppError { ··· 687 688 sketch_version: SketchVersion, 688 689 generation: Option<GeometryGeneration>, 689 690 failed: bool, 691 + error: Option<ExtrudeError>, 692 + } 693 + 694 + impl ExtrudePreview { 695 + fn status(&self) -> ExtrudeStatus<'_> { 696 + match &self.error { 697 + Some(error) => ExtrudeStatus::Failed(error), 698 + None => ExtrudeStatus::Valid, 699 + } 700 + } 690 701 } 691 702 692 703 const PREVIEW_CHORD_MM: f64 = 0.05; ··· 748 759 let first_preview = state.extrude_preview.is_none(); 749 760 let preview = compute_extrude_preview(&mut state.feature_cache, &state.document, feature); 750 761 let generation = preview.as_ref().and_then(EvaluatedExtrude::generation); 751 - let mut failure = preview 762 + let error = preview 752 763 .as_ref() 753 - .and_then(|evaluated| match evaluated.result() { 754 - Ok(_) => None, 755 - Err(error) => Some(error.to_string()), 756 - }); 764 + .and_then(|evaluated| evaluated.result().as_ref().err().cloned()); 765 + let mut failure = error.as_ref().map(ToString::to_string); 757 766 if generation != previous_generation { 758 767 state.solid_view = match preview.as_ref().and_then(EvaluatedExtrude::solid) { 759 768 Some(solid) => match build_solid_view(solid) { ··· 772 781 sketch_version, 773 782 generation, 774 783 failed: now_failed, 784 + error, 775 785 }); 776 786 let newly_failed = first_preview || !previously_failed; 777 787 if let Some(detail) = failure.filter(|_| newly_failed) { ··· 2778 2788 a11y, 2779 2789 &mut state.chrome_shaper, 2780 2790 ); 2791 + let extrude_status = state.extrude_preview.as_ref().map(ExtrudePreview::status); 2781 2792 let frame = state.shell.render( 2782 2793 &mut ctx, 2783 2794 &state.document, ··· 2787 2798 layout_size, 2788 2799 cursor_world, 2789 2800 state.camera3.filter(|_| state.solid_view.is_some()), 2801 + extrude_status, 2790 2802 &mut state.view, 2791 2803 ); 2792 2804 let dim_outcome = pending_dim(&state.mode).map(|pending| { ··· 4091 4103 sketch_version: base_version, 4092 4104 generation: None, 4093 4105 failed: false, 4106 + error: None, 4094 4107 }; 4095 4108 assert!(super::extrude_preview_is_current( 4096 4109 Some(&cached),
+53 -7
crates/bone-app/src/shell.rs
··· 48 48 }; 49 49 use crate::smart_dimension; 50 50 use crate::status_badge::{ 51 - render_status_panel, status_badge_widget_id, status_color, status_label_key, 52 - status_panel_widget_id, 51 + ExtrudeStatus, extrude_badge_style, extrude_badge_widget_id, extrude_panel_widget_id, 52 + render_extrude_panel, render_status_panel, status_badge_widget_id, status_color, 53 + status_label_key, status_panel_widget_id, 53 54 }; 54 55 use crate::strings; 55 56 use crate::view_cube::{ ··· 284 285 last_mode_was_sketch: bool, 285 286 pub status_panel_open: bool, 286 287 pub status_panel: PanelState, 288 + pub extrude_panel_open: bool, 289 + pub extrude_panel: PanelState, 287 290 status_cache: Option<(SketchVersion, SketchStatusReport)>, 288 291 pub ribbon_overflow_open: BTreeMap<WidgetId, bool>, 289 292 pub ribbon_active_tab: Option<WidgetId>, ··· 549 552 viewport_size: LayoutSize, 550 553 cursor_world: Option<Point2>, 551 554 camera3: Option<Camera3>, 555 + extrude_status: Option<ExtrudeStatus<'_>>, 552 556 view: &mut ViewUi, 553 557 ) -> ShellFrame { 554 558 let theme = ctx.theme(); ··· 665 669 None 666 670 }; 667 671 let status_badge_id = status_badge_widget_id(self.ids.status_bar); 668 - let status_activated = render_status_bar( 672 + let extrude_badge_id = extrude_badge_widget_id(self.ids.status_bar); 673 + let badge_activated = render_status_bar( 669 674 ctx, 670 675 status_rect, 671 676 self.ids.status_bar, ··· 674 679 cursor_world, 675 680 status_report, 676 681 status_badge_id, 682 + extrude_status, 683 + extrude_badge_id, 677 684 &mut paints, 678 685 ); 679 - if status_activated { 686 + if badge_activated == Some(status_badge_id) { 680 687 self.state.status_panel_open = !self.state.status_panel_open; 681 688 } 682 689 if status_report.is_none_or(|r| r.offending().is_empty()) { ··· 697 704 self.state.status_panel_open = false; 698 705 } 699 706 } 707 + if badge_activated == Some(extrude_badge_id) { 708 + self.state.extrude_panel_open = !self.state.extrude_panel_open; 709 + } 710 + let extrude_error = extrude_status.and_then(ExtrudeStatus::error); 711 + if self.state.extrude_panel_open { 712 + match extrude_error { 713 + Some(error) => render_extrude_panel( 714 + ctx, 715 + extrude_panel_widget_id(self.ids.status_bar), 716 + &mut self.state.extrude_panel, 717 + status_rect, 718 + error, 719 + &mut popover_paints, 720 + ), 721 + None => self.state.extrude_panel_open = false, 722 + } 723 + } 700 724 let confirm_visible = 701 725 mode.is_sketch() || matches!(mode, Mode::Extrude(ExtrudeArming::Profile { .. })); 702 726 let confirm = ··· 2574 2598 cursor_world: Option<Point2>, 2575 2599 status_report: Option<&SketchStatusReport>, 2576 2600 status_badge_id: WidgetId, 2601 + extrude_status: Option<ExtrudeStatus<'_>>, 2602 + extrude_badge_id: WidgetId, 2577 2603 paints: &mut Vec<WidgetPaint>, 2578 - ) -> bool { 2604 + ) -> Option<WidgetId> { 2579 2605 if rect.size.width.value() <= 0.0 || rect.size.height.value() <= 0.0 { 2580 - return false; 2606 + return None; 2581 2607 } 2582 2608 let mode_label = mode_status_label(ctx.strings, mode, document); 2583 2609 let mode_item = StatusItem::with_text( ··· 2617 2643 .interactive(has_panel_content) 2618 2644 .badge(status_color(report.status(), &ctx.theme().cad)) 2619 2645 }); 2646 + let extrude_item = extrude_status.map(|status| { 2647 + let (label, color) = extrude_badge_style(status, &ctx.theme().cad); 2648 + StatusItem::new( 2649 + extrude_badge_id, 2650 + label, 2651 + StatusAlign::End, 2652 + STATUS_STATUS_WIDTH, 2653 + ) 2654 + .interactive(status.error().is_some()) 2655 + .badge(color) 2656 + }); 2620 2657 let mut items: Vec<StatusItem> = vec![mode_item]; 2621 2658 if let Some(coords) = coords_item { 2622 2659 items.push(coords); ··· 2624 2661 if let Some(status) = status_item { 2625 2662 items.push(status); 2626 2663 } 2664 + if let Some(extrude) = extrude_item { 2665 + items.push(extrude); 2666 + } 2627 2667 items.push(units_item); 2628 2668 let response = show_status_bar( 2629 2669 ctx, 2630 2670 StatusBar::new(id, rect, strings::STATUS_BAR_LABEL, &items), 2631 2671 ); 2632 2672 paints.extend(response.paint); 2633 - response.activated == Some(status_badge_id) 2673 + response 2674 + .activated 2675 + .filter(|id| *id == status_badge_id || *id == extrude_badge_id) 2634 2676 } 2635 2677 2636 2678 fn mode_status_label(strings_table: &StringTable, mode: &Mode, document: &Document) -> LabelText { ··· 3347 3389 selection, 3348 3390 &Settings::default(), 3349 3391 size, 3392 + None, 3350 3393 None, 3351 3394 None, 3352 3395 &mut ViewUi::default(), ··· 4010 4053 layout_size(1600.0, 900.0), 4011 4054 None, 4012 4055 None, 4056 + None, 4013 4057 &mut ViewUi::default(), 4014 4058 ); 4015 4059 let any_smart_dim_label = frame.paints.iter().any(|p| { ··· 4446 4490 selection, 4447 4491 &Settings::default(), 4448 4492 layout_size(1280.0, 800.0), 4493 + None, 4449 4494 None, 4450 4495 None, 4451 4496 &mut ViewUi::default(), ··· 5059 5104 selection, 5060 5105 &Settings::default(), 5061 5106 canvas, 5107 + None, 5062 5108 None, 5063 5109 None, 5064 5110 &mut ViewUi::default(),
+217 -4
crates/bone-app/src/status_badge.rs
··· 1 - use bone_document::{Sketch, SketchEntityKind, SketchRelation, SketchStatusReport}; 2 - use bone_types::{SketchDimensionId, SketchEntityId, SketchItemId, SketchRelationId, SketchStatus}; 1 + use bone_document::{ 2 + BrepError, ExtrudeError, ProfileDefect, Sketch, SketchEntityKind, SketchRelation, 3 + SketchStatusReport, TruckGap, 4 + }; 5 + use bone_types::{ 6 + BrepSlot, SketchDimensionId, SketchEntityId, SketchItemId, SketchRelationId, SketchStatus, 7 + }; 3 8 use bone_ui::frame::FrameCtx; 4 9 use bone_ui::layout::{LayoutPos, LayoutPx, LayoutRect, LayoutSize}; 5 10 use bone_ui::strings::{StringKey, StringTable}; ··· 42 47 } 43 48 } 44 49 50 + #[derive(Copy, Clone, Debug)] 51 + pub enum ExtrudeStatus<'a> { 52 + Valid, 53 + Failed(&'a ExtrudeError), 54 + } 55 + 56 + impl<'a> ExtrudeStatus<'a> { 57 + #[must_use] 58 + pub fn error(self) -> Option<&'a ExtrudeError> { 59 + match self { 60 + Self::Valid => None, 61 + Self::Failed(error) => Some(error), 62 + } 63 + } 64 + } 65 + 66 + #[must_use] 67 + pub fn extrude_badge_style(status: ExtrudeStatus<'_>, cad: &CadColors) -> (StringKey, Color) { 68 + match status { 69 + ExtrudeStatus::Valid => (strings::STATUS_EXTRUDE_VALID, cad.sketch_fully_defined), 70 + ExtrudeStatus::Failed(ExtrudeError::Kernel( 71 + BrepError::DanglingEdge { .. } | BrepError::DanglingVertex { .. }, 72 + )) => (strings::STATUS_EXTRUDE_DANGLING, cad.sketch_dangling), 73 + ExtrudeStatus::Failed(_) => (strings::STATUS_EXTRUDE_INVALID, cad.sketch_invalid), 74 + } 75 + } 76 + 77 + fn extrude_panel_line(error: &ExtrudeError, strings_table: &StringTable) -> LabelText { 78 + match error { 79 + ExtrudeError::UnsolvedSketch(_) => LabelText::Key(strings::EXTRUDE_PANEL_UNSOLVED_SKETCH), 80 + ExtrudeError::Kernel(kernel) => kernel_panel_line(kernel, strings_table), 81 + } 82 + } 83 + 84 + fn kernel_panel_line(error: &BrepError, strings_table: &StringTable) -> LabelText { 85 + match error { 86 + BrepError::InvalidProfile { reason } => LabelText::Key(profile_defect_key(*reason)), 87 + BrepError::EmptyExtrudeDepth => LabelText::Key(strings::EXTRUDE_PANEL_EMPTY_DEPTH), 88 + BrepError::ShellNotClosed => LabelText::Key(strings::EXTRUDE_PANEL_SHELL_OPEN), 89 + BrepError::DegenerateEdge { edge } => brep_entity_line( 90 + strings::EXTRUDE_PANEL_DEGENERATE_EDGE, 91 + strings_table, 92 + edge.slot(), 93 + ), 94 + BrepError::DanglingEdge { edge } => brep_entity_line( 95 + strings::EXTRUDE_PANEL_DANGLING_EDGE, 96 + strings_table, 97 + edge.slot(), 98 + ), 99 + BrepError::DanglingVertex { vertex } => brep_entity_line( 100 + strings::EXTRUDE_PANEL_DANGLING_VERTEX, 101 + strings_table, 102 + vertex.slot(), 103 + ), 104 + BrepError::TruckUnsupported { detail } => LabelText::Key(truck_gap_key(*detail)), 105 + BrepError::MissingLabel { .. } 106 + | BrepError::ReattachMismatch { .. } 107 + | BrepError::ReattachOrder 108 + | BrepError::BlobSerialize 109 + | BrepError::BlobParse 110 + | BrepError::StepSyntax 111 + | BrepError::StepNoData 112 + | BrepError::StepShellMalformed 113 + | BrepError::StepEmpty 114 + | BrepError::StepMultipleSolids { .. } 115 + | BrepError::StepUnsupported { .. } => LabelText::Key(strings::EXTRUDE_PANEL_INTERNAL), 116 + } 117 + } 118 + 119 + fn profile_defect_key(defect: ProfileDefect) -> StringKey { 120 + match defect { 121 + ProfileDefect::OpenLoop => strings::EXTRUDE_PANEL_PROFILE_OPEN_LOOP, 122 + ProfileDefect::BranchingVertex => strings::EXTRUDE_PANEL_PROFILE_BRANCHING, 123 + ProfileDefect::SelfIntersectingLoop => strings::EXTRUDE_PANEL_PROFILE_SELF_INTERSECTING, 124 + ProfileDefect::ZeroArea => strings::EXTRUDE_PANEL_PROFILE_ZERO_AREA, 125 + ProfileDefect::UncontainedLoop => strings::EXTRUDE_PANEL_PROFILE_UNCONTAINED, 126 + ProfileDefect::OverlappingLoops => strings::EXTRUDE_PANEL_PROFILE_OVERLAPPING, 127 + } 128 + } 129 + 130 + fn truck_gap_key(gap: TruckGap) -> StringKey { 131 + match gap { 132 + TruckGap::ReverseNormal | TruckGap::AxisDirection | TruckGap::ReferenceDirection => { 133 + strings::EXTRUDE_PANEL_UNSUPPORTED_DIRECTION 134 + } 135 + TruckGap::ThroughAll 136 + | TruckGap::UpToNext 137 + | TruckGap::UpToVertex 138 + | TruckGap::UpToSurface 139 + | TruckGap::OffsetFromSurface 140 + | TruckGap::UpToBody => strings::EXTRUDE_PANEL_UNSUPPORTED_END, 141 + TruckGap::Draft => strings::EXTRUDE_PANEL_UNSUPPORTED_DRAFT, 142 + TruckGap::ThinWall => strings::EXTRUDE_PANEL_UNSUPPORTED_THIN, 143 + } 144 + } 145 + 146 + fn brep_entity_line(key: StringKey, strings_table: &StringTable, slot: BrepSlot) -> LabelText { 147 + LabelText::Owned(strings_table.resolve(key).replace("{n}", &slot.to_string())) 148 + } 149 + 45 150 #[must_use] 46 151 pub fn status_panel_rect(status_bar: LayoutRect, row_count: usize) -> LayoutRect { 47 152 let rows = row_count.clamp(1, STATUS_PANEL_MAX_ROWS); ··· 71 176 paints: &mut Vec<WidgetPaint>, 72 177 ) { 73 178 let lines = compose_panel_lines(report, sketch, ctx.strings); 179 + render_diagnostics_panel( 180 + ctx, 181 + panel_id, 182 + panel_state, 183 + status_bar_rect, 184 + strings::STATUS_PANEL_TITLE, 185 + lines, 186 + paints, 187 + ); 188 + } 189 + 190 + pub fn render_extrude_panel( 191 + ctx: &mut FrameCtx<'_>, 192 + panel_id: WidgetId, 193 + panel_state: &mut PanelState, 194 + status_bar_rect: LayoutRect, 195 + error: &ExtrudeError, 196 + paints: &mut Vec<WidgetPaint>, 197 + ) { 198 + let lines = vec![extrude_panel_line(error, ctx.strings)]; 199 + render_diagnostics_panel( 200 + ctx, 201 + panel_id, 202 + panel_state, 203 + status_bar_rect, 204 + strings::EXTRUDE_PANEL_TITLE, 205 + lines, 206 + paints, 207 + ); 208 + } 209 + 210 + fn render_diagnostics_panel( 211 + ctx: &mut FrameCtx<'_>, 212 + panel_id: WidgetId, 213 + panel_state: &mut PanelState, 214 + status_bar_rect: LayoutRect, 215 + title: StringKey, 216 + lines: Vec<LabelText>, 217 + paints: &mut Vec<WidgetPaint>, 218 + ) { 74 219 let rect = status_panel_rect(status_bar_rect, lines.len()); 75 220 let response = show_panel( 76 221 ctx, 77 222 Panel::new(panel_id, rect, panel_state) 78 223 .variant(PanelVariant::Card) 79 224 .titlebar(PanelTitlebar { 80 - label: strings::STATUS_PANEL_TITLE, 225 + label: title, 81 226 height: LayoutPx::new(STATUS_PANEL_TITLE_HEIGHT_PX), 82 227 collapsible: false, 83 228 }), ··· 230 375 parent.child(WidgetKey::new("status.panel")) 231 376 } 232 377 378 + #[must_use] 379 + pub fn extrude_badge_widget_id(parent: WidgetId) -> WidgetId { 380 + parent.child(WidgetKey::new("status.extrude.badge")) 381 + } 382 + 383 + #[must_use] 384 + pub fn extrude_panel_widget_id(parent: WidgetId) -> WidgetId { 385 + parent.child(WidgetKey::new("status.extrude.panel")) 386 + } 387 + 233 388 #[cfg(test)] 234 389 mod tests { 235 - use super::{SketchStatus, status_color, status_label_key}; 390 + use super::{ 391 + BrepError, ExtrudeError, ExtrudeStatus, SketchStatus, TruckGap, extrude_badge_style, 392 + extrude_panel_line, status_color, status_label_key, 393 + }; 236 394 use crate::strings; 395 + use bone_types::BrepEdgeId; 396 + use bone_ui::strings::Locale; 237 397 use bone_ui::theme::Theme; 398 + 399 + #[test] 400 + fn extrude_badge_maps_states_to_keys_and_colors() { 401 + let cad = Theme::light().cad; 402 + let dangling = ExtrudeError::Kernel(BrepError::DanglingEdge { 403 + edge: BrepEdgeId::default(), 404 + }); 405 + let invalid = ExtrudeError::Kernel(BrepError::EmptyExtrudeDepth); 406 + assert_eq!( 407 + extrude_badge_style(ExtrudeStatus::Valid, &cad), 408 + (strings::STATUS_EXTRUDE_VALID, cad.sketch_fully_defined) 409 + ); 410 + assert_eq!( 411 + extrude_badge_style(ExtrudeStatus::Failed(&dangling), &cad), 412 + (strings::STATUS_EXTRUDE_DANGLING, cad.sketch_dangling) 413 + ); 414 + assert_eq!( 415 + extrude_badge_style(ExtrudeStatus::Failed(&invalid), &cad), 416 + (strings::STATUS_EXTRUDE_INVALID, cad.sketch_invalid) 417 + ); 418 + } 419 + 420 + #[test] 421 + fn invalid_and_dangling_tokens_differ() { 422 + let cad = Theme::light().cad; 423 + assert_ne!(cad.sketch_invalid, cad.sketch_dangling); 424 + assert_ne!(cad.sketch_invalid, cad.sketch_over_defined); 425 + } 426 + 427 + #[test] 428 + fn extrude_panel_lines_render_through_string_table() { 429 + let table = strings::make_strings(Locale::EnUs); 430 + let depth = ExtrudeError::Kernel(BrepError::EmptyExtrudeDepth); 431 + assert_eq!( 432 + extrude_panel_line(&depth, &table).resolve(&table), 433 + "Extrude depth is zero" 434 + ); 435 + let dangling = ExtrudeError::Kernel(BrepError::DanglingEdge { 436 + edge: BrepEdgeId::default(), 437 + }); 438 + let text = extrude_panel_line(&dangling, &table) 439 + .resolve(&table) 440 + .to_owned(); 441 + assert!(text.starts_with("Edge ")); 442 + assert!(!text.contains("{n}")); 443 + let draft = ExtrudeError::Kernel(BrepError::TruckUnsupported { 444 + detail: TruckGap::Draft, 445 + }); 446 + assert_eq!( 447 + extrude_panel_line(&draft, &table).resolve(&table), 448 + "Draft is not supported yet" 449 + ); 450 + } 238 451 239 452 #[test] 240 453 fn each_status_maps_to_expected_label_key() {
+151
crates/bone-app/src/strings.rs
··· 228 228 pub const PROPERTY_ROW_EXTRUDE_MERGE: StringKey = StringKey::new("property.row.extrude.merge"); 229 229 pub const EXTRUDE_END_BLIND: StringKey = StringKey::new("extrude.end.blind"); 230 230 pub const EXTRUDE_END_MIDPLANE: StringKey = StringKey::new("extrude.end.midplane"); 231 + pub const STATUS_EXTRUDE_VALID: StringKey = StringKey::new("status.extrude.valid"); 232 + pub const STATUS_EXTRUDE_INVALID: StringKey = StringKey::new("status.extrude.invalid"); 233 + pub const STATUS_EXTRUDE_DANGLING: StringKey = StringKey::new("status.extrude.dangling"); 234 + pub const EXTRUDE_PANEL_TITLE: StringKey = StringKey::new("extrude.panel.title"); 235 + pub const EXTRUDE_PANEL_UNSOLVED_SKETCH: StringKey = 236 + StringKey::new("extrude.panel.unsolved_sketch"); 237 + pub const EXTRUDE_PANEL_PROFILE_OPEN_LOOP: StringKey = 238 + StringKey::new("extrude.panel.profile.open_loop"); 239 + pub const EXTRUDE_PANEL_PROFILE_BRANCHING: StringKey = 240 + StringKey::new("extrude.panel.profile.branching"); 241 + pub const EXTRUDE_PANEL_PROFILE_SELF_INTERSECTING: StringKey = 242 + StringKey::new("extrude.panel.profile.self_intersecting"); 243 + pub const EXTRUDE_PANEL_PROFILE_ZERO_AREA: StringKey = 244 + StringKey::new("extrude.panel.profile.zero_area"); 245 + pub const EXTRUDE_PANEL_PROFILE_UNCONTAINED: StringKey = 246 + StringKey::new("extrude.panel.profile.uncontained"); 247 + pub const EXTRUDE_PANEL_PROFILE_OVERLAPPING: StringKey = 248 + StringKey::new("extrude.panel.profile.overlapping"); 249 + pub const EXTRUDE_PANEL_EMPTY_DEPTH: StringKey = StringKey::new("extrude.panel.empty_depth"); 250 + pub const EXTRUDE_PANEL_SHELL_OPEN: StringKey = StringKey::new("extrude.panel.shell_open"); 251 + pub const EXTRUDE_PANEL_DEGENERATE_EDGE: StringKey = 252 + StringKey::new("extrude.panel.degenerate_edge"); 253 + pub const EXTRUDE_PANEL_DANGLING_EDGE: StringKey = StringKey::new("extrude.panel.dangling_edge"); 254 + pub const EXTRUDE_PANEL_DANGLING_VERTEX: StringKey = 255 + StringKey::new("extrude.panel.dangling_vertex"); 256 + pub const EXTRUDE_PANEL_UNSUPPORTED_DIRECTION: StringKey = 257 + StringKey::new("extrude.panel.unsupported.direction"); 258 + pub const EXTRUDE_PANEL_UNSUPPORTED_END: StringKey = 259 + StringKey::new("extrude.panel.unsupported.end_condition"); 260 + pub const EXTRUDE_PANEL_UNSUPPORTED_DRAFT: StringKey = 261 + StringKey::new("extrude.panel.unsupported.draft"); 262 + pub const EXTRUDE_PANEL_UNSUPPORTED_THIN: StringKey = 263 + StringKey::new("extrude.panel.unsupported.thin"); 264 + pub const EXTRUDE_PANEL_INTERNAL: StringKey = StringKey::new("extrude.panel.internal"); 231 265 pub const PROPERTY_ROW_KIND: StringKey = StringKey::new("property.row.kind"); 232 266 pub const PROPERTY_ROW_X: StringKey = StringKey::new("property.row.x"); 233 267 pub const PROPERTY_ROW_Y: StringKey = StringKey::new("property.row.y"); ··· 506 540 (PROPERTY_ROW_EXTRUDE_MERGE, "Merge Result"), 507 541 (EXTRUDE_END_BLIND, "Blind"), 508 542 (EXTRUDE_END_MIDPLANE, "Mid Plane"), 543 + (STATUS_EXTRUDE_VALID, "Valid Solid"), 544 + (STATUS_EXTRUDE_INVALID, "Invalid Solid"), 545 + (STATUS_EXTRUDE_DANGLING, "Dangling"), 546 + (EXTRUDE_PANEL_TITLE, "Extrude Diagnostics"), 547 + (EXTRUDE_PANEL_UNSOLVED_SKETCH, "Sketch does not solve"), 548 + (EXTRUDE_PANEL_PROFILE_OPEN_LOOP, "Profile has an open loop"), 549 + ( 550 + EXTRUDE_PANEL_PROFILE_BRANCHING, 551 + "Profile has a branching vertex", 552 + ), 553 + ( 554 + EXTRUDE_PANEL_PROFILE_SELF_INTERSECTING, 555 + "Profile loop self-intersects", 556 + ), 557 + (EXTRUDE_PANEL_PROFILE_ZERO_AREA, "Profile has zero area"), 558 + ( 559 + EXTRUDE_PANEL_PROFILE_UNCONTAINED, 560 + "Profile loop lies outside the outer boundary", 561 + ), 562 + ( 563 + EXTRUDE_PANEL_PROFILE_OVERLAPPING, 564 + "Profile inner loops overlap", 565 + ), 566 + (EXTRUDE_PANEL_EMPTY_DEPTH, "Extrude depth is zero"), 567 + (EXTRUDE_PANEL_SHELL_OPEN, "Solid shell is not closed"), 568 + ( 569 + EXTRUDE_PANEL_DEGENERATE_EDGE, 570 + "Edge {n} collapses below tolerance", 571 + ), 572 + (EXTRUDE_PANEL_DANGLING_EDGE, "Edge {n} belongs to no loop"), 573 + ( 574 + EXTRUDE_PANEL_DANGLING_VERTEX, 575 + "Vertex {n} belongs to no loop", 576 + ), 577 + ( 578 + EXTRUDE_PANEL_UNSUPPORTED_DIRECTION, 579 + "Extrude direction is not supported yet", 580 + ), 581 + ( 582 + EXTRUDE_PANEL_UNSUPPORTED_END, 583 + "End condition is not supported yet", 584 + ), 585 + ( 586 + EXTRUDE_PANEL_UNSUPPORTED_DRAFT, 587 + "Draft is not supported yet", 588 + ), 589 + ( 590 + EXTRUDE_PANEL_UNSUPPORTED_THIN, 591 + "Thin feature is not supported yet", 592 + ), 593 + (EXTRUDE_PANEL_INTERNAL, "Internal kernel error"), 509 594 (PROPERTY_ROW_KIND, "Type"), 510 595 (PROPERTY_ROW_X, "X"), 511 596 (PROPERTY_ROW_Y, "Y"), ··· 793 878 (PROPERTY_ROW_EXTRUDE_MERGE, "[!! Mêrge Rêsult !!]"), 794 879 (EXTRUDE_END_BLIND, "[!! Blînd !!]"), 795 880 (EXTRUDE_END_MIDPLANE, "[!! Mîd Plâne !!]"), 881 + (STATUS_EXTRUDE_VALID, "[!! Vâlid Sôlid !!]"), 882 + (STATUS_EXTRUDE_INVALID, "[!! Invâlid Sôlid !!]"), 883 + (STATUS_EXTRUDE_DANGLING, "[!! Dângling !!]"), 884 + (EXTRUDE_PANEL_TITLE, "[!! Êxtrude Diagnôstics !!]"), 885 + ( 886 + EXTRUDE_PANEL_UNSOLVED_SKETCH, 887 + "[!! Skêtch dôes nôt sôlve !!]", 888 + ), 889 + ( 890 + EXTRUDE_PANEL_PROFILE_OPEN_LOOP, 891 + "[!! Prôfile hâs an ôpen lôop !!]", 892 + ), 893 + ( 894 + EXTRUDE_PANEL_PROFILE_BRANCHING, 895 + "[!! Prôfile hâs a brânching vêrtex !!]", 896 + ), 897 + ( 898 + EXTRUDE_PANEL_PROFILE_SELF_INTERSECTING, 899 + "[!! Prôfile lôop sêlf-intersêcts !!]", 900 + ), 901 + ( 902 + EXTRUDE_PANEL_PROFILE_ZERO_AREA, 903 + "[!! Prôfile hâs zêro ârea !!]", 904 + ), 905 + ( 906 + EXTRUDE_PANEL_PROFILE_UNCONTAINED, 907 + "[!! Prôfile lôop lîes outsîde the ôuter bôundary !!]", 908 + ), 909 + ( 910 + EXTRUDE_PANEL_PROFILE_OVERLAPPING, 911 + "[!! Prôfile înner lôops overlâp !!]", 912 + ), 913 + (EXTRUDE_PANEL_EMPTY_DEPTH, "[!! Êxtrude dêpth is zêro !!]"), 914 + ( 915 + EXTRUDE_PANEL_SHELL_OPEN, 916 + "[!! Sôlid shêll is nôt clôsed !!]", 917 + ), 918 + ( 919 + EXTRUDE_PANEL_DEGENERATE_EDGE, 920 + "[!! Êdge {n} collâpses belôw tolerânce !!]", 921 + ), 922 + ( 923 + EXTRUDE_PANEL_DANGLING_EDGE, 924 + "[!! Êdge {n} belôngs to nô lôop !!]", 925 + ), 926 + ( 927 + EXTRUDE_PANEL_DANGLING_VERTEX, 928 + "[!! Vêrtex {n} belôngs to nô lôop !!]", 929 + ), 930 + ( 931 + EXTRUDE_PANEL_UNSUPPORTED_DIRECTION, 932 + "[!! Êxtrude dirêction is nôt suppôrted yêt !!]", 933 + ), 934 + ( 935 + EXTRUDE_PANEL_UNSUPPORTED_END, 936 + "[!! Énd condîtion is nôt suppôrted yêt !!]", 937 + ), 938 + ( 939 + EXTRUDE_PANEL_UNSUPPORTED_DRAFT, 940 + "[!! Drâft is nôt suppôrted yêt !!]", 941 + ), 942 + ( 943 + EXTRUDE_PANEL_UNSUPPORTED_THIN, 944 + "[!! Thîn fêature is nôt suppôrted yêt !!]", 945 + ), 946 + (EXTRUDE_PANEL_INTERNAL, "[!! Intêrnal kêrnel êrror !!]"), 796 947 (PROPERTY_ROW_KIND, "[!! Týpe !!]"), 797 948 (PROPERTY_ROW_X, "X"), 798 949 (PROPERTY_ROW_Y, "Y"),
+3 -2
crates/bone-document/src/lib.rs
··· 6 6 pub mod undo; 7 7 8 8 pub use bone_kernel::{ 9 - BrepSolid, DraftAngle, DraftDirection, DraftMagnitude, ExtrudeDirection, ExtrudeEndCondition, 10 - ExtrudeFeature, ExtrudeSense, MergeResult, ThinWall, ThinWallDirection, 9 + BrepError, BrepSolid, DraftAngle, DraftDirection, DraftMagnitude, ExtrudeDirection, 10 + ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense, MergeResult, ProfileDefect, ThinWall, 11 + ThinWallDirection, TruckGap, 11 12 }; 12 13 pub use document::{ 13 14 Document, DocumentHeader, DocumentParameters, ExtrudeFile, FeatureEdge, FeatureNode,
+34 -2
crates/bone-types/src/lib.rs
··· 127 127 }; 128 128 } 129 129 130 - impl_as_u64!(SketchId, ExtrudeId); 130 + impl_as_u64!(SketchId, ExtrudeId, BrepEdgeId, BrepVertexId); 131 + 132 + #[derive(Copy, Clone, Debug, PartialEq, Eq)] 133 + pub struct BrepSlot(u32); 134 + 135 + impl core::fmt::Display for BrepSlot { 136 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 137 + self.0.fmt(f) 138 + } 139 + } 140 + 141 + macro_rules! impl_slot { 142 + ($($key:ty),+ $(,)?) => { 143 + $(impl $key { 144 + #[must_use] 145 + pub fn slot(self) -> BrepSlot { 146 + use slotmap::Key; 147 + let low = self.data().as_ffi() & u64::from(u32::MAX); 148 + BrepSlot(u32::try_from(low).expect("masked value fits in u32")) 149 + } 150 + })+ 151 + }; 152 + } 153 + 154 + impl_slot!(BrepEdgeId, BrepVertexId); 131 155 132 156 #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] 133 157 pub struct Tolerance(f64); ··· 340 364 341 365 #[cfg(test)] 342 366 mod tests { 367 + use super::BrepSlot; 343 368 use super::{ 344 369 Aabb3, Angle, AngleTolerance, AxisAngle, BodyId, BrepEdgeId, BrepFaceId, BrepLoopId, 345 370 BrepShellId, BrepVertexId, Camera3, ChordHeightTolerance, CreaseAngle, CubicEasing, ··· 352 377 StepOrganization, StepOriginatingSystem, StepSchema, Tolerance, UnitVec2, UnitVec3, Vec2, 353 378 Vec3, VertexId, VertexLabel, VertexRole, WireId, ZoomFactor, degree, millimeter, radian, 354 379 }; 355 - use slotmap::Key; 380 + use slotmap::{Key, SlotMap}; 356 381 use uom::si::length::meter; 357 382 358 383 fn ortho_camera() -> Camera3 { ··· 394 419 assert!(BrepEdgeId::null().is_null()); 395 420 assert!(BrepVertexId::null().is_null()); 396 421 assert!(BrepLoopId::null().is_null()); 422 + } 423 + 424 + #[test] 425 + fn brep_slot_is_the_one_based_insertion_index() { 426 + let mut edges: SlotMap<BrepEdgeId, ()> = SlotMap::with_key(); 427 + let slots: Vec<BrepSlot> = (0..3).map(|_| edges.insert(()).slot()).collect(); 428 + assert_eq!(slots, vec![BrepSlot(1), BrepSlot(2), BrepSlot(3)]); 397 429 } 398 430 399 431 #[test]
+2
crates/bone-ui/src/theme/color.rs
··· 376 376 pub sketch_under_defined: Color, 377 377 pub sketch_fully_defined: Color, 378 378 pub sketch_over_defined: Color, 379 + pub sketch_invalid: Color, 379 380 pub sketch_dangling: Color, 380 381 pub sketch_driven: Color, 381 382 pub selection_primary: Color, ··· 391 392 sketch_under_defined: Color::from_srgb_u8(0x1F, 0x4F, 0xCC), 392 393 sketch_fully_defined: Color::from_srgb_u8(0x10, 0x10, 0x10), 393 394 sketch_over_defined: Color::from_srgb_u8(0xC8, 0x00, 0x1E), 395 + sketch_invalid: Color::from_srgb_u8(0x7A, 0x00, 0x14), 394 396 sketch_dangling: Color::from_srgb_u8(0x80, 0x80, 0x00), 395 397 sketch_driven: Color::from_srgb_u8(0x7E, 0x7E, 0x7E), 396 398 selection_primary: Color::from_srgb_u8(0x2C, 0xCD, 0x33),
+6
crates/bone-ui/tests/snapshots/theme_snapshot__theme_dark.snap
··· 391 391 b: 0.012983031, 392 392 a: 1.0, 393 393 ), 394 + sketch_invalid: Color( 395 + r: 0.19461781, 396 + g: 0.0, 397 + b: 0.0069954083, 398 + a: 1.0, 399 + ), 394 400 sketch_dangling: Color( 395 401 r: 0.21586053, 396 402 g: 0.21586053,
+6
crates/bone-ui/tests/snapshots/theme_snapshot__theme_light.snap
··· 391 391 b: 0.012983031, 392 392 a: 1.0, 393 393 ), 394 + sketch_invalid: Color( 395 + r: 0.19461781, 396 + g: 0.0, 397 + b: 0.0069954083, 398 + a: 1.0, 399 + ), 394 400 sketch_dangling: Color( 395 401 r: 0.21586053, 396 402 g: 0.21586053,