fix(block-editor): edit and preview authoring parameters

This commit is contained in:
Karim Jerbi committed 2026-10-18 19:07:06 +01:00
1 parent 2797b5b9e1
commit 4fdf357a7e
2 files changed
+173 -28

No files matched your search

+156 -4
View File
@@ -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<crate::scene::WireModel> {
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<Message> {
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));
+17 -24
View File
@@ -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<ui::properties::LocalizedChoice>,
> {
) -> rustc_hash::FxHashMap<String, iced::widget::combo_box::State<ui::properties::LocalizedChoice>>
{
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<Handle> {
// 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);