diff --git a/src/app/block_authoring_edit.rs b/src/app/block_authoring_edit.rs index efc4c542c..9a7248cb9 100644 --- a/src/app/block_authoring_edit.rs +++ b/src/app/block_authoring_edit.rs @@ -14,6 +14,7 @@ struct EditCommand { definition: Handle, handle: Handle, part: Part, + document_snapshot: codec::CadDocument, } impl CadCommand for EditCommand { fn name(&self) -> &'static str { @@ -32,11 +33,160 @@ impl CadCommand for EditCommand { position, } } + fn on_preview_wires(&mut self, position: DVec3) -> Vec { + let mut staged = self.document_snapshot.clone(); + let at = codec::types::Vector3::new(position.x, position.y, 0.0); + if edit::move_part(&mut staged, self.definition, self.handle, self.part, at).is_err() { + return Vec::new(); + } + let Some(item) = edit::items(&staged, self.definition) + .into_iter() + .find(|item| item.handle == self.handle) + else { + return Vec::new(); + }; + item.graphic + .lines + .into_iter() + .map(|[a, b]| { + crate::scene::WireModel::solid_f64( + format!("authoring-preview-{}", self.handle.value()), + vec![[a.x, a.y, a.z], [b.x, b.y, b.z]], + crate::scene::WireModel::CYAN, + false, + ) + }) + .collect() + } fn on_enter(&mut self) -> CmdResult { CmdResult::Cancel } } impl OpenCADStudio { + pub(super) fn commit_authoring_name(&mut self) -> Task { + let i = self.active_tab; + let key = crate::ui::properties::FieldKey::Geom("authoring_name"); + let Some(name) = self.tabs[i] + .properties + .edit_buf + .remove(&key) + .map(|s| s.trim().to_owned()) + else { + return Task::none(); + }; + if name.is_empty() { + self.command_line + .push_error(crate::t!("Parameter name cannot be empty.").as_ref()); + return Task::none(); + } + let Some(handle) = self.tabs[i].selected_authoring else { + return Task::none(); + }; + let Some(session) = self.tabs[i].active_block_edit_session() else { + return Task::none(); + }; + let definition = session.br_handle; + let doc = &self.tabs[i].scene.document; + if edit::items(doc, definition) + .iter() + .any(|item| item.handle != handle && item.name.eq_ignore_ascii_case(&name)) + { + self.command_line + .push_error(crate::t!("Parameter names must be unique.").as_ref()); + return Task::none(); + } + let mut staged = doc.clone(); + let Some(codec::objects::ObjectType::DynamicBlock(object)) = + staged.objects.get_mut(&handle) + else { + return Task::none(); + }; + let changed = match &mut object.data { + codec::objects::DynamicBlockData::PointParameter(p) => { + p.position_name = name; + true + } + codec::objects::DynamicBlockData::LinearParameter(p) => { + p.distance_name = name; + true + } + codec::objects::DynamicBlockData::PolarParameter(p) => { + p.distance_name = name; + true + } + codec::objects::DynamicBlockData::XYParameter(p) => { + p.x_label = name; + true + } + codec::objects::DynamicBlockData::RotationParameter(p) => { + p.angle_name = name; + true + } + codec::objects::DynamicBlockData::LookupParameter(p) => { + p.lookup_name = name; + true + } + _ => false, + }; + if changed { + self.push_undo_snapshot(i, "Rename block parameter"); + self.tabs[i].scene.document = staged; + self.tabs[i].dirty = true; + self.tabs[i].scene.bump_geometry(); + self.finish_pending_history(i); + self.refresh_properties(); + } + Task::none() + } + pub(super) fn pick_authoring_box(&mut self, a: Point, b: Point, crossing: bool) -> bool { + let i = self.active_tab; + let Some(session) = self.tabs[i].active_block_edit_session() else { + return false; + }; + let items = edit::items(&self.tabs[i].scene.document, session.br_handle); + let bounds = iced::Rectangle { + x: 0.0, + y: 0.0, + width: self.tabs[i].scene.selection.borrow().view.vp_size.0, + height: self.tabs[i].scene.selection.borrow().view.vp_size.1, + }; + let camera = self.tabs[i].scene.camera.borrow(); + let min = Point::new(a.x.min(b.x), a.y.min(b.y)); + let max = Point::new(a.x.max(b.x), a.y.max(b.y)); + let hit = items + .into_iter() + .filter_map(|item| { + let mut projected = Vec::new(); + for (_, p) in &item.grips { + if let Some(p) = camera.project(DVec3::new(p.x, p.y, p.z), bounds) { + projected.push(Point::new(p.x, p.y)); + } + } + for p in item.graphic.lines.iter().flat_map(|line| line.iter()) { + if let Some(p) = camera.project(DVec3::new(p.x, p.y, p.z), bounds) { + projected.push(Point::new(p.x, p.y)); + } + } + let any = projected + .iter() + .any(|p| p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y); + let all = !projected.is_empty() + && projected + .iter() + .all(|p| p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y); + (if crossing { any } else { all }).then_some(item.handle) + }) + .next(); + drop(camera); + if let Some(handle) = hit { + self.tabs[i].selected_authoring = Some(handle); + self.tabs[i].scene.deselect_all(); + self.refresh_properties(); + true + } else { + false + } + } pub(super) fn pick_authoring_object(&mut self, p: Point, bounds: Rectangle) -> bool { let i = self.active_tab; let tab = &self.tabs[i]; @@ -87,8 +237,12 @@ impl OpenCADStudio { .min_by(|a, b| a.0.total_cmp(&b.0)); drop(camera); if let Some((_, handle, name)) = hit { + if self.shift_down && self.tabs[i].selected_authoring == Some(handle) { + self.tabs[i].selected_authoring = None; + } else { + self.tabs[i].selected_authoring = Some(handle); + } self.tabs[i].scene.deselect_all(); - self.tabs[i].selected_authoring = (!self.shift_down).then_some(handle); self.tabs[i] .scene .selection @@ -102,9 +256,6 @@ impl OpenCADStudio { )); true } else { - if self.tabs[i].selected_authoring.take().is_some() { - self.refresh_properties(); - } false } } @@ -156,6 +307,7 @@ impl OpenCADStudio { definition: session.br_handle, handle, part, + document_snapshot: self.tabs[i].scene.document.clone(), }; self.command_line.push_info(&command.prompt()); self.tabs[i].active_cmd = Some(Box::new(command)); diff --git a/src/app/properties.rs b/src/app/properties.rs index daf941511..6f401851f 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -15,10 +15,8 @@ const MAX_PROP_AGGREGATE: usize = 2_000; fn property_choice_combos( sections: &[crate::scene::model::object::PropSection], -) -> rustc_hash::FxHashMap< - String, - iced::widget::combo_box::State, -> { +) -> rustc_hash::FxHashMap> +{ sections .iter() .flat_map(|section| section.props.iter()) @@ -2473,13 +2471,16 @@ filter={:.1} local={:.1} aggregate={:.1} entities={}", .find(|item| item.handle == handle) }) { use crate::scene::model::object::{PropSection, PropValue, Property}; - panel.title = item.name; + panel.title = item.name.clone(); panel.sections = vec![PropSection { title: crate::t!("Geometry").into_owned(), - props: item - .grips - .into_iter() - .map(|(part, point)| Property { + props: std::iter::once(Property { + label: crate::t!("Parameter name").into_owned(), + field: "authoring_name", + value: PropValue::PlainText(item.name.clone()), + }) + .chain(item.grips.into_iter().map(|(part, point)| { + Property { label: crate::t!(match part { crate::scene::block_authoring::edit::Part::Point => "Position", crate::scene::block_authoring::edit::Part::Base => "Base point", @@ -2496,8 +2497,9 @@ filter={:.1} local={:.1} aggregate={:.1} entities={}", "{:.4}, {:.4}, {:.4}", point.x, point.y, point.z )), - }) - .collect(), + } + })) + .collect(), }]; } } @@ -2912,6 +2914,7 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={} // Append the dynamic-block visibility (lookup) grip, if the lone // selection is a visibility-parametric block reference. self.refresh_visibility_grip(wo); + self.refresh_table_grip(wo); self.refresh_test_action_grips(wo); self.refresh_authoring_grips(wo); } @@ -2919,9 +2922,7 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={} pub(super) fn property_target_handles(&self, i: usize) -> Vec { // The node graph edits its own node's entity through the panel's // handlers, independent of the drawing selection. - self.resolve_property_targets(i, true, |scene, handle| { - !scene.is_layer_locked(handle) - }) + self.resolve_property_targets(i, true, |scene, handle| !scene.is_layer_locked(handle)) } /// The point clouds the Locked row applies to: locked ones too, unless @@ -4693,11 +4694,7 @@ mod chprop_integration_tests { app.finish_pending_history(i); let undo_before = app.tabs[i].history.undo_stack.len(); - let color_before = app.tabs[i] - .scene - .document - .header - .current_entity_color; + let color_before = app.tabs[i].scene.document.header.current_entity_color; app.tabs[i].dirty = false; let _ = app.update(crate::app::Message::PropColorChanged( codec::types::Color::Index(9), @@ -4705,11 +4702,7 @@ mod chprop_integration_tests { app.finish_pending_history(i); assert_eq!(app.tabs[i].history.undo_stack.len(), undo_before); assert_eq!( - app.tabs[i] - .scene - .document - .header - .current_entity_color, + app.tabs[i].scene.document.header.current_entity_color, color_before ); assert!(!app.tabs[i].dirty);