forked from 1b-agent/opencadstudio-upstream
feat(block-editor): select and edit native parameter and action grips
Parameters and Move/Scale action markers were drawn as overlays with no selectable authoring objects. Pick their points, labels or graphics in BEDIT to display editable definition, endpoint, reference and label grips using the existing selection overlay and point-input commands. Show the selected object's coordinates in the Properties panel. Write edits to the native parameter/grip/action records while preserving graph identities and target bindings. Maintain Rotation radii and oriented grip directions, synchronize visibility lookup data, and retain Basepoint attribute relocation with recorded entity history. Escape cancels edits or clears selection; editor undo/redo and save/discard preserve the native changes. Validate with self-contained viewport-input and native DWG/DXF persistence regressions, all ten parameter types, action bindings, cancellation and Basepoint attribute undo. Checks: 3 focused, 323 block (6 existing opt-in ignored), 39 dock and 23 viewport tests passed; counts overlap. Ordinary drawing-reference grips and per-instance saved action evaluation follow in the next milestone.
This commit is contained in:
1 parent
f69aefc3df
commit
677ff0e08c
11 files changed
+991
No files matched your search
@@ -0,0 +1,230 @@
|
||||
use super::{Message, OpenCADStudio};
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::block_authoring::edit::{self, Part};
|
||||
use codec::Handle;
|
||||
use glam::DVec3;
|
||||
use iced::{Point, Rectangle, Task};
|
||||
|
||||
const EDIT_GRIP_START: usize = usize::MAX / 4;
|
||||
pub(super) fn is_editor_grip(id: usize) -> bool {
|
||||
(EDIT_GRIP_START..EDIT_GRIP_START + 16).contains(&id)
|
||||
}
|
||||
struct EditCommand {
|
||||
document: u64,
|
||||
definition: Handle,
|
||||
handle: Handle,
|
||||
part: Part,
|
||||
}
|
||||
impl CadCommand for EditCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"BEDITGRIP"
|
||||
}
|
||||
fn prompt(&self) -> String {
|
||||
crate::t!("Block Editor Specify the new authoring grip location (Escape cancels):")
|
||||
.into_owned()
|
||||
}
|
||||
fn on_point(&mut self, position: DVec3) -> CmdResult {
|
||||
CmdResult::EditBlockAuthoring {
|
||||
document: self.document,
|
||||
definition: self.definition,
|
||||
handle: self.handle,
|
||||
part: self.part,
|
||||
position,
|
||||
}
|
||||
}
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
}
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn pick_authoring_object(&mut self, p: Point, bounds: Rectangle) -> bool {
|
||||
let i = self.active_tab;
|
||||
let tab = &self.tabs[i];
|
||||
if tab.active_cmd.is_some() || tab.active_grip.is_some() {
|
||||
return false;
|
||||
}
|
||||
let Some(session) = tab.active_block_edit_session() else {
|
||||
return false;
|
||||
};
|
||||
let items = edit::items(&tab.scene.document, session.br_handle);
|
||||
let camera = tab.scene.camera.borrow();
|
||||
let project = |v: codec::types::Vector3| {
|
||||
camera
|
||||
.project(DVec3::new(v.x, v.y, v.z), bounds)
|
||||
.map(|v| Point::new(v.x, v.y))
|
||||
};
|
||||
let segment = |a: Point, b: Point| {
|
||||
let ab = glam::Vec2::new(b.x - a.x, b.y - a.y);
|
||||
let ap = glam::Vec2::new(p.x - a.x, p.y - a.y);
|
||||
let t = (ap.dot(ab) / ab.length_squared().max(1e-12)).clamp(0., 1.);
|
||||
(ap - ab * t).length()
|
||||
};
|
||||
let hit = items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let mut d = f32::INFINITY;
|
||||
for (_, v) in &item.grips {
|
||||
if let Some(v) = project(*v) {
|
||||
d = d.min(segment(v, v));
|
||||
}
|
||||
}
|
||||
for [a, b] in &item.graphic.lines {
|
||||
if let (Some(a), Some(b)) = (project(*a), project(*b)) {
|
||||
d = d.min(segment(a, b));
|
||||
}
|
||||
}
|
||||
for (v, label) in &item.graphic.labels {
|
||||
if let Some(v) = project(*v) {
|
||||
// Labels are drawn starting beside their anchor, in screen pixels.
|
||||
let width = (label.chars().count() as f32 * 7.).max(20.);
|
||||
if p.x >= v.x - 8. && p.x <= v.x + width + 12. && (p.y - v.y).abs() < 16. {
|
||||
d = d.min(0.);
|
||||
}
|
||||
}
|
||||
}
|
||||
(d <= 10.).then_some((d, item.handle, item.name.clone()))
|
||||
})
|
||||
.min_by(|a, b| a.0.total_cmp(&b.0));
|
||||
drop(camera);
|
||||
if let Some((_, handle, name)) = hit {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].selected_authoring = (!self.shift_down).then_some(handle);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.clear_left_selection_gesture();
|
||||
self.refresh_properties();
|
||||
self.command_line.push_info(&format!(
|
||||
"{} — {}",
|
||||
name,
|
||||
crate::t!("Select a grip to edit its location.")
|
||||
));
|
||||
true
|
||||
} else {
|
||||
if self.tabs[i].selected_authoring.take().is_some() {
|
||||
self.refresh_properties();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
pub(super) fn refresh_authoring_grips(&mut self, wo: [f64; 3]) {
|
||||
let i = self.active_tab;
|
||||
let Some(handle) = self.tabs[i].selected_authoring else {
|
||||
return;
|
||||
};
|
||||
let item = self.tabs[i].active_block_edit_session().and_then(|s| {
|
||||
edit::items(&self.tabs[i].scene.document, s.br_handle)
|
||||
.into_iter()
|
||||
.find(|p| p.handle == handle)
|
||||
});
|
||||
let Some(item) = item.filter(|_| self.tabs[i].scene.selected.is_empty()) else {
|
||||
self.tabs[i].selected_authoring = None;
|
||||
return;
|
||||
};
|
||||
for (index, (_, point)) in item.grips.into_iter().enumerate() {
|
||||
self.tabs[i]
|
||||
.selected_grips
|
||||
.push(crate::scene::model::object::GripDef {
|
||||
id: EDIT_GRIP_START + index,
|
||||
world: DVec3::new(point.x - wo[0], point.y - wo[1], point.z - wo[2]),
|
||||
is_midpoint: false,
|
||||
shape: crate::scene::model::object::GripShape::Square,
|
||||
dir: None,
|
||||
axis: None,
|
||||
});
|
||||
self.tabs[i].selected_grip_handles.push(handle);
|
||||
}
|
||||
}
|
||||
pub(super) fn start_authoring_grip(&mut self, handle: Handle, id: usize) -> bool {
|
||||
if !is_editor_grip(id) {
|
||||
return false;
|
||||
}
|
||||
let i = self.active_tab;
|
||||
let Some(session) = self.tabs[i].active_block_edit_session() else {
|
||||
return false;
|
||||
};
|
||||
let Some(part) = edit::items(&self.tabs[i].scene.document, session.br_handle)
|
||||
.into_iter()
|
||||
.find(|p| p.handle == handle)
|
||||
.and_then(|p| p.grips.get(id - EDIT_GRIP_START).map(|(part, _)| *part))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let command = EditCommand {
|
||||
document: self.tabs[i].id,
|
||||
definition: session.br_handle,
|
||||
handle,
|
||||
part,
|
||||
};
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
self.grip_hover = None;
|
||||
self.grip_popup = None;
|
||||
true
|
||||
}
|
||||
pub(super) fn handle_authoring_edit(&mut self, result: CmdResult) -> Task<Message> {
|
||||
let CmdResult::EditBlockAuthoring {
|
||||
document,
|
||||
definition,
|
||||
handle,
|
||||
part,
|
||||
position,
|
||||
} = result
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].id != document
|
||||
|| self.tabs[i]
|
||||
.active_block_edit_session()
|
||||
.map(|s| s.br_handle)
|
||||
!= Some(definition)
|
||||
{
|
||||
self.command_line
|
||||
.push_error(crate::t!("The Block Editor session is no longer active.").as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
let mut staged = self.tabs[i].scene.document.clone();
|
||||
match edit::move_part(
|
||||
&mut staged,
|
||||
definition,
|
||||
handle,
|
||||
part,
|
||||
codec::types::Vector3::new(position.x, position.y, position.z),
|
||||
) {
|
||||
Ok(changed) => {
|
||||
if changed {
|
||||
self.push_undo_snapshot(i, "Block authoring grip");
|
||||
// Keep the live entity-change recorder attached. Basepoint edits
|
||||
// can also relocate INSERT attributes outside the edited block.
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
let changed_entities = staged
|
||||
.entities()
|
||||
.filter(|e| doc.get_entity(e.common().handle) != Some(*e))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for entity in changed_entities {
|
||||
let handle = entity.common().handle;
|
||||
*doc.get_entity_mut(handle)
|
||||
.expect("authoring edits retain entity identities") = entity;
|
||||
}
|
||||
for (h, o) in staged.objects.iter() {
|
||||
if doc.objects.get(h) != Some(o) {
|
||||
doc.objects.insert(*h, o.clone());
|
||||
}
|
||||
}
|
||||
doc.block_visibility_params = staged.block_visibility_params.clone();
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.finish_pending_history(i);
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.refresh_properties();
|
||||
}
|
||||
Err(e) => self.command_line.push_error(crate::t!(e).as_ref()),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,179 @@ fn feed(app: &mut OpenCADStudio, text: &str) {
|
||||
let _ = app.update(Message::CommandSubmit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authoring_edit_basepoint_attribute_relocation_undo_and_cancel() {
|
||||
let (mut app, reference) = fixture();
|
||||
begin(&mut app, reference);
|
||||
let i = app.active_tab;
|
||||
let _ = app.run_command_line("BPARAMETER BASEPOINT 2,3");
|
||||
let def = app.tabs[i].active_block_edit_session().unwrap().br_handle;
|
||||
let handle = crate::scene::block_basepoint::parameter(&app.tabs[i].scene.document, def)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut attribute = codec::entities::AttributeEntity::new("TAG".into(), "value".into());
|
||||
attribute.insertion_point = Vector3::new(7., 8., 0.);
|
||||
let EntityType::Insert(insert) = app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity_mut(reference)
|
||||
.unwrap()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
insert.attributes.push(attribute);
|
||||
let before = app.tabs[i].scene.document.clone();
|
||||
let result = crate::command::CmdResult::EditBlockAuthoring {
|
||||
document: app.tabs[i].id,
|
||||
definition: def,
|
||||
handle,
|
||||
part: crate::scene::block_authoring::edit::Part::Point,
|
||||
position: glam::DVec3::new(4., 6., 0.),
|
||||
};
|
||||
let _ = app.handle_authoring_edit(result);
|
||||
assert_ne!(
|
||||
app.tabs[i].scene.document.get_entity(reference),
|
||||
before.get_entity(reference)
|
||||
);
|
||||
app.undo_steps(1);
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
app.redo_steps(1);
|
||||
assert_ne!(
|
||||
app.tabs[i].scene.document.get_entity(reference),
|
||||
before.get_entity(reference)
|
||||
);
|
||||
app.tabs[i].selected_authoring = Some(handle);
|
||||
app.refresh_properties();
|
||||
let id = app.tabs[i].selected_grips[0].id;
|
||||
assert!(app.start_authoring_grip(handle, id));
|
||||
let after = app.tabs[i].scene.document.clone();
|
||||
let depth = app.tabs[i].history.undo_stack.len();
|
||||
let _ = app.update(Message::CommandEscape);
|
||||
assert_eq!(app.tabs[i].scene.document, after);
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth);
|
||||
let _ = app.update(Message::CommandEscape);
|
||||
assert_eq!(app.tabs[i].selected_authoring, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authoring_edit_mouse_selection_endpoint_label_marker_history_and_reopen() {
|
||||
use crate::scene::block_authoring::edit::{self, Part};
|
||||
use crate::scene::block_actions::{self as actions, Kind};
|
||||
let (mut app, reference) = fixture();
|
||||
begin(&mut app, reference);
|
||||
let i = app.active_tab;
|
||||
let _ = app.run_command_line("BPARAMETER LINEAR 2,2 6,2 4,3");
|
||||
let def = app.tabs[i].active_block_edit_session().unwrap().br_handle;
|
||||
let p = actions::parameters(&app.tabs[i].scene.document, def, Kind::Move)[0].clone();
|
||||
let targets = members(&app);
|
||||
let a = actions::create(
|
||||
&mut app.tabs[i].scene.document,
|
||||
def,
|
||||
Kind::Move,
|
||||
p.handle,
|
||||
&targets,
|
||||
glam::DVec3::new(5., 5., 0.),
|
||||
)
|
||||
.unwrap();
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (1600., 900.);
|
||||
app.tabs[i].scene.camera.borrow_mut().fit_to_bounds_f64(
|
||||
glam::DVec3::new(-1., -1., 0.),
|
||||
glam::DVec3::new(10., 7., 0.),
|
||||
1600. / 900.,
|
||||
);
|
||||
let click = |app: &mut OpenCADStudio, point: Vector3| {
|
||||
let scene = &app.tabs[app.active_tab].scene;
|
||||
let (w, h) = scene.selection.borrow().view.vp_size;
|
||||
let b = scene.active_model_tile_bounds(w, h);
|
||||
let s = scene
|
||||
.camera
|
||||
.borrow()
|
||||
.project(
|
||||
glam::DVec3::new(point.x, point.y, point.z),
|
||||
iced::Rectangle {
|
||||
x: 0.,
|
||||
y: 0.,
|
||||
width: b.width,
|
||||
height: b.height,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = app.update(Message::ViewportLeftPressAt(iced::Point::new(
|
||||
s.x + b.x,
|
||||
s.y + b.y,
|
||||
)));
|
||||
let _ = app.update(Message::ViewportLeftRelease);
|
||||
};
|
||||
click(&mut app, Vector3::new(4., 3., 0.));
|
||||
assert_eq!(app.tabs[i].selected_authoring, Some(p.handle));
|
||||
assert_eq!(app.tabs[i].selected_grips.len(), 3);
|
||||
let before = app.tabs[i].scene.document.clone();
|
||||
let depth = app.tabs[i].history.undo_stack.len();
|
||||
click(&mut app, Vector3::new(6., 2., 0.));
|
||||
assert_eq!(
|
||||
app.tabs[i].active_cmd.as_ref().map(|c| c.name()),
|
||||
Some("BEDITGRIP")
|
||||
);
|
||||
feed(&mut app, "8,2");
|
||||
assert!(app.tabs[i].active_cmd.is_none());
|
||||
assert_eq!(
|
||||
actions::parameters(&app.tabs[i].scene.document, def, Kind::Move)[0].end,
|
||||
glam::DVec3::new(8., 2., 0.)
|
||||
);
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth + 1);
|
||||
app.undo_steps(1);
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
app.redo_steps(1);
|
||||
let label = edit::items(&app.tabs[i].scene.document, def)
|
||||
.into_iter()
|
||||
.find(|i| i.handle == p.handle)
|
||||
.unwrap()
|
||||
.grips
|
||||
.into_iter()
|
||||
.find(|(p, _)| *p == Part::Label(0))
|
||||
.unwrap()
|
||||
.1;
|
||||
click(&mut app, label);
|
||||
feed(&mut app, "5,4");
|
||||
let graphic = edit::items(&app.tabs[i].scene.document, def)
|
||||
.into_iter()
|
||||
.find(|i| i.handle == p.handle)
|
||||
.unwrap();
|
||||
assert_eq!(graphic.graphic.labels[0].0, Vector3::new(5., 4., 0.));
|
||||
let _ = app.update(Message::CommandEscape);
|
||||
click(&mut app, Vector3::new(5., 5., 0.));
|
||||
assert_eq!(app.tabs[i].selected_authoring, Some(a));
|
||||
click(&mut app, Vector3::new(5., 5., 0.));
|
||||
feed(&mut app, "7,5");
|
||||
let action = actions::actions(&app.tabs[i].scene.document, def);
|
||||
assert_eq!(action[0].1.display_location, Vector3::new(7., 5., 0.));
|
||||
assert_eq!(action[0].1.dependencies, targets);
|
||||
let _ = app.dispatch_command("BSAVE");
|
||||
let _ = app.dispatch_command("BEDIT_DISCARD");
|
||||
assert!(app.tabs[i]
|
||||
.selected_grips
|
||||
.iter()
|
||||
.all(|g| !super::block_authoring_edit::is_editor_grip(g.id)));
|
||||
// Persisted native coordinates, rather than session-only display positions.
|
||||
use std::io::Cursor;
|
||||
let bytes = codec::DwgWriter::write_to_vec(&app.tabs[i].scene.document).unwrap();
|
||||
let loaded = codec::DwgReader::from_stream(Cursor::new(bytes))
|
||||
.read()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
actions::parameters(&loaded, def, Kind::Move)[0].end,
|
||||
glam::DVec3::new(8., 2., 0.)
|
||||
);
|
||||
assert_eq!(
|
||||
actions::actions(&loaded, def)[0].1.display_location,
|
||||
Vector3::new(7., 5., 0.)
|
||||
);
|
||||
app.tabs[i].scene.document = loaded;
|
||||
begin(&mut app, reference);
|
||||
click(&mut app, Vector3::new(8., 2., 0.));
|
||||
assert_eq!(app.tabs[i].selected_authoring, Some(p.handle));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_actions_mouse_selection_waits_for_enter_for_move_and_scale() {
|
||||
use crate::scene::block_actions::{self as actions, Kind};
|
||||
|
||||
@@ -575,6 +575,9 @@ impl OpenCADStudio {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
result @ CmdResult::EditBlockAuthoring { .. } => {
|
||||
return self.handle_authoring_edit(result);
|
||||
}
|
||||
result @ (CmdResult::PlaceBlockAction { .. }
|
||||
| CmdResult::EvaluateBlockParameter { .. }) => {
|
||||
return self.handle_block_action_result(result);
|
||||
|
||||
@@ -182,6 +182,7 @@ pub(super) struct DocumentTab {
|
||||
/// the active grip edit instead of committing it.
|
||||
pub(super) grip_base_pending: bool,
|
||||
pub(super) selected_handle: Option<Handle>,
|
||||
pub(super) selected_authoring: Option<Handle>,
|
||||
/// Dynamic-block visibility grip for the current single selection.
|
||||
pub(super) visibility_grip: Option<super::visibility::VisibilityGrip>,
|
||||
pub(super) wireframe: bool,
|
||||
@@ -657,6 +658,7 @@ impl DocumentTab {
|
||||
grip_copy: false,
|
||||
grip_base_pending: false,
|
||||
selected_handle: None,
|
||||
selected_authoring: None,
|
||||
visibility_grip: None,
|
||||
wireframe: false,
|
||||
render_mode: codec::entities::ViewportRenderMode::Wireframe2D,
|
||||
|
||||
@@ -15,6 +15,7 @@ mod block_save_as;
|
||||
mod block_visibility;
|
||||
mod block_lookup;
|
||||
mod block_actions;
|
||||
mod block_authoring_edit;
|
||||
mod block_properties;
|
||||
#[cfg(test)]
|
||||
mod block_properties_tests;
|
||||
|
||||
@@ -2475,6 +2475,45 @@ filter={:.1} local={:.1} aggregate={:.1} entities={}",
|
||||
}
|
||||
}
|
||||
};
|
||||
if selected.is_empty() {
|
||||
if let Some(item) = self.tabs[i].selected_authoring.and_then(|handle| {
|
||||
let session = self.tabs[i].active_block_edit_session()?;
|
||||
crate::scene::block_authoring::edit::items(
|
||||
&self.tabs[i].scene.document,
|
||||
session.br_handle,
|
||||
)
|
||||
.into_iter()
|
||||
.find(|item| item.handle == handle)
|
||||
}) {
|
||||
use crate::scene::model::object::{PropSection, PropValue, Property};
|
||||
panel.title = item.name;
|
||||
panel.sections = vec![PropSection {
|
||||
title: crate::t!("Geometry").into_owned(),
|
||||
props: 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",
|
||||
crate::scene::block_authoring::edit::Part::End => "End point",
|
||||
crate::scene::block_authoring::edit::Part::Reference =>
|
||||
"Reference point",
|
||||
crate::scene::block_authoring::edit::Part::Label(0) =>
|
||||
"Label position",
|
||||
_ => "Second label position",
|
||||
})
|
||||
.into_owned(),
|
||||
field: "authoring_grip_position",
|
||||
value: PropValue::ReadOnly(format!(
|
||||
"{:.4}, {:.4}, {:.4}",
|
||||
point.x, point.y, point.z
|
||||
)),
|
||||
})
|
||||
.collect(),
|
||||
}];
|
||||
}
|
||||
}
|
||||
// Precompute the focused-id → field-key map for O(1) lookups on
|
||||
// `PropSyncActive`; derived from `sections`, so rebuild it here.
|
||||
panel.field_key_by_id = crate::ui::properties::build_field_key_map(&panel.sections);
|
||||
@@ -2887,6 +2926,7 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
// selection is a visibility-parametric block reference.
|
||||
self.refresh_visibility_grip(wo);
|
||||
self.refresh_test_action_grips(wo);
|
||||
self.refresh_authoring_grips(wo);
|
||||
}
|
||||
|
||||
pub(super) fn property_target_handles(&self, i: usize) -> Vec<Handle> {
|
||||
|
||||
@@ -1410,6 +1410,7 @@ impl OpenCADStudio {
|
||||
// ESC while in MSPACE → exit back to paper space.
|
||||
return Task::done(Message::ExitViewport);
|
||||
} else {
|
||||
self.tabs[i].selected_authoring = None;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refresh_properties();
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
|
||||
@@ -832,6 +832,7 @@ impl OpenCADStudio {
|
||||
.filter(|(_, grip)| {
|
||||
grip.id != crate::app::visibility::VIS_GRIP_ID
|
||||
&& !crate::app::block_actions::is_test_grip(grip.id)
|
||||
&& !crate::app::block_authoring_edit::is_editor_grip(grip.id)
|
||||
})
|
||||
.map(|(owner, grip)| GripTarget {
|
||||
handle: owner,
|
||||
@@ -857,6 +858,7 @@ impl OpenCADStudio {
|
||||
.filter(|(_, grip)| {
|
||||
grip.id != crate::app::visibility::VIS_GRIP_ID
|
||||
&& !crate::app::block_actions::is_test_grip(grip.id)
|
||||
&& !crate::app::block_authoring_edit::is_editor_grip(grip.id)
|
||||
})
|
||||
.filter(|(_, grip)| (grip.world - world).length_squared() <= epsilon_sq)
|
||||
.map(|(owner, grip)| GripTarget {
|
||||
@@ -1000,6 +1002,7 @@ impl OpenCADStudio {
|
||||
if self.grip_popup.is_none()
|
||||
&& grip_id != crate::app::visibility::VIS_GRIP_ID
|
||||
&& !crate::app::block_actions::is_test_grip(grip_id)
|
||||
&& !crate::app::block_authoring_edit::is_editor_grip(grip_id)
|
||||
&& self
|
||||
.grip_hover
|
||||
.as_ref()
|
||||
@@ -4003,6 +4006,9 @@ impl OpenCADStudio {
|
||||
if self.start_test_action_grip(handle, grip_id) {
|
||||
return Task::none();
|
||||
}
|
||||
if self.start_authoring_grip(handle, grip_id) {
|
||||
return Task::none();
|
||||
}
|
||||
if self.shift_down {
|
||||
let key = (handle, grip_id);
|
||||
if !self.tabs[i].hot_grips.remove(&key) {
|
||||
@@ -4121,6 +4127,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
if edit_cam.is_none() && self.pick_authoring_object(p, bounds) {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
// Ctrl+click on a composite solid picks the solid of it under the
|
||||
// cursor — the one the Properties palette and grips then edit.
|
||||
if self.ctrl_down
|
||||
|
||||
Reference in new issue
Block a user