fix(block-editor): manage parameter and basepoint graph items

This commit is contained in:
Karim Jerbi committed 2026-10-18 19:07:10 +01:00
1 parent 4fdf357a7e
commit 286bdf6426
2 files changed
+163 -14

No files matched your search

+162 -13
View File
@@ -93,12 +93,31 @@ pub fn items(doc: &CadDocument, definition: Handle) -> Vec<Item> {
p.parameter.definition_point, p.parameter.definition_point,
)); ));
} }
if let Some((handle, p)) = crate::scene::block_basepoint::parameter(doc, definition) { let basepoint = crate::scene::block_basepoint::parameter(doc, definition)
result.push(single( .map(|(handle, p)| (handle, p.parameter.definition_point))
.or_else(|| {
let name = doc
.block_records
.iter()
.find(|record| record.handle == definition)?
.name
.as_str();
Some((
definition,
crate::scene::render_graph::block_base_point(doc, name),
))
});
if let Some((handle, point)) = basepoint {
let label = point + Vector3::new(0.25, 0.25, 0.0);
let mut graphic = Graphic::default();
graphic.lines.push([point, label]);
graphic.labels.push((label, "Basepoint".into()));
result.push(Item {
handle, handle,
"Basepoint".into(), name: "Basepoint".into(),
p.parameter.definition_point, graphic,
)); grips: vec![(Part::Point, point)],
});
} }
if let Some(p) = crate::scene::block_visibility::parameter(doc, definition) { if let Some(p) = crate::scene::block_visibility::parameter(doc, definition) {
result.push(single(p.handle, p.name.clone(), p.def_point)); result.push(single(p.handle, p.name.clone(), p.def_point));
@@ -178,13 +197,16 @@ pub fn move_part(
if (at - old).length() < 1e-10 { if (at - old).length() < 1e-10 {
return Ok(false); return Ok(false);
} }
let graph = graph(doc, definition)?.ok_or("The authoring graph is unavailable.")?;
let mut staged = doc.clone(); let mut staged = doc.clone();
if crate::scene::block_basepoint::parameter(doc, definition).is_some_and(|(h, _)| h == handle) { if handle == definition
|| crate::scene::block_basepoint::parameter(doc, definition)
.is_some_and(|(h, _)| h == handle)
{
crate::scene::block_basepoint::create(&mut staged, definition, at)?; crate::scene::block_basepoint::create(&mut staged, definition, at)?;
*doc = staged; *doc = staged;
return Ok(true); return Ok(true);
} }
let graph = graph(doc, definition)?.ok_or("The authoring graph is unavailable.")?;
let mut moved_grips = vec![]; let mut moved_grips = vec![];
match staged match staged
.objects .objects
@@ -287,12 +309,8 @@ pub fn move_part(
p.definition_label_point = at p.definition_label_point = at
} }
} }
DynamicBlockData::MoveAction(p) => { DynamicBlockData::MoveAction(p) => p.action.display_location = at,
p.action.display_location = at DynamicBlockData::ArrayAction(p) => p.action.display_location = at,
}
DynamicBlockData::ArrayAction(p) => {
p.action.display_location = at
}
DynamicBlockData::StretchAction(p) => p.action.display_location = at, DynamicBlockData::StretchAction(p) => p.action.display_location = at,
DynamicBlockData::FlipAction(p) => p.action.display_location = at, DynamicBlockData::FlipAction(p) => p.action.display_location = at,
DynamicBlockData::ScaleAction(p) | DynamicBlockData::RotateAction(p) => { DynamicBlockData::ScaleAction(p) | DynamicBlockData::RotateAction(p) => {
@@ -412,6 +430,137 @@ pub fn move_part(
*doc = staged; *doc = staged;
Ok(true) Ok(true)
} }
/// Remove an authoring parameter together with the graph expressions that it
/// owns. Action bindings are reciprocal graph edges, so they are detached from
/// both ends before the parameter nodes are removed.
pub fn remove_parameter(
doc: &mut CadDocument,
definition: Handle,
handle: Handle,
) -> Result<(), &'static str> {
let item = items(doc, definition)
.into_iter()
.find(|item| item.handle == handle)
.ok_or("The selected authoring parameter is no longer available.")?;
if handle == definition && crate::scene::block_basepoint::parameter(doc, definition).is_none() {
return Err("The block basepoint cannot be deleted.");
}
if matches!(
doc.objects.get(&handle),
Some(ObjectType::DynamicBlock(object))
if matches!(object.data, DynamicBlockData::MoveAction(_) | DynamicBlockData::ArrayAction(_) | DynamicBlockData::StretchAction(_) | DynamicBlockData::FlipAction(_) | DynamicBlockData::ScaleAction(_) | DynamicBlockData::RotateAction(_))
) || matches!(
doc.objects.get(&handle),
Some(ObjectType::BlockVisibilityParameter(_))
) {
return Err("Delete actions and visibility parameters from their dedicated editor.");
}
let graph_handle = graph(doc, definition)?.ok_or("The authoring graph is unavailable.")?;
let ObjectType::DynamicBlock(graph_object) = doc.objects.get(&graph_handle).unwrap() else {
unreachable!()
};
let DynamicBlockData::EvaluationGraph(original) = &graph_object.data else {
unreachable!()
};
let mut graph = original.clone();
let parameter_nodes = graph
.nodes
.iter()
.filter(|node| node.expression == handle)
.map(|node| node.id)
.collect::<Vec<_>>();
if parameter_nodes.len() != 1 {
return Err("The selected parameter has an invalid graph node.");
}
let parameter_node = parameter_nodes[0];
let mut owned_nodes = vec![parameter_node];
// Native authoring groups have one grip edge (grip -> parameter) and up
// to two component edges (parameter -> grip location components). Classify
// endpoint objects as well as edge direction so unrelated graph edges
// cannot cause another parameter to be erased.
for edge in graph.edges.iter().filter(|edge| edge.next_id == 0) {
if edge.destination_node == parameter_node
&& graph.nodes.iter().find(|node| node.id == edge.source_node).is_some_and(|node| {
matches!(doc.objects.get(&node.expression), Some(ObjectType::DynamicBlock(object)) if matches!(object.data, DynamicBlockData::LinearGrip(_) | DynamicBlockData::PolarGrip(_) | DynamicBlockData::XYGrip(_) | DynamicBlockData::RotationGrip(_) | DynamicBlockData::AlignmentGrip(_) | DynamicBlockData::FlipGrip(_) | DynamicBlockData::LookupGrip(_) | DynamicBlockData::VisibilityGrip(_)))
})
{
owned_nodes.push(edge.source_node);
} else if edge.source_node == parameter_node
&& graph.nodes.iter().find(|node| node.id == edge.destination_node).is_some_and(|node| {
matches!(doc.objects.get(&node.expression), Some(ObjectType::DynamicBlock(object)) if matches!(object.data, DynamicBlockData::GripLocationComponent(_)))
})
{
owned_nodes.push(edge.destination_node);
}
}
owned_nodes.sort_unstable();
owned_nodes.dedup();
let mut edge_ids = graph
.edges
.iter()
.filter(|edge| {
owned_nodes.contains(&edge.source_node) || owned_nodes.contains(&edge.destination_node)
})
.map(|edge| edge.id)
.collect::<std::collections::BTreeSet<_>>();
// Action relationships use reciprocal next_id=4 edges. Remove their
// inverse too, while leaving the action's graph node and object intact.
let selected_edges = edge_ids.iter().copied().collect::<Vec<_>>();
for selected in selected_edges {
let Some(edge) = graph.edges.iter().find(|edge| edge.id == selected) else {
continue;
};
if edge.next_id == 4 {
let inverse = graph
.edges
.iter()
.find(|candidate| candidate.id == edge.outgoing_edges[4]);
let Some(inverse) = inverse.filter(|inverse| {
inverse.next_id == 4
&& inverse.outgoing_edges[4] == edge.id
&& inverse.source_node == edge.destination_node
&& inverse.destination_node == edge.source_node
}) else {
return Err("The parameter has an invalid action binding; it was not deleted.");
};
edge_ids.insert(inverse.id);
}
}
for edge in edge_ids {
crate::scene::block_authoring::graph::remove(&mut graph, edge);
}
let removed_handles = owned_nodes
.iter()
.filter_map(|node_id| {
graph
.nodes
.iter()
.find(|node| node.id == *node_id)
.map(|node| node.expression)
})
.collect::<Vec<_>>();
graph.nodes.retain(|node| !owned_nodes.contains(&node.id));
if let Some(max) = graph.nodes.iter().map(|node| node.next_id).max() {
graph.first_node_id = max;
graph.first_node_id_copy = max;
} else {
graph.first_node_id = 0;
graph.first_node_id_copy = 0;
}
if let Some(ObjectType::DynamicBlock(object)) = doc.objects.get_mut(&graph_handle) {
object.data = DynamicBlockData::EvaluationGraph(graph);
}
for owned in removed_handles.into_iter().chain(std::iter::once(handle)) {
if owned != graph_handle {
doc.objects.remove(&owned);
}
}
doc.block_visibility_params.remove(&handle);
let _ = item;
Ok(())
}
fn label_offset(p: &BlockTwoPointParameter, at: Vector3) -> f64 { fn label_offset(p: &BlockTwoPointParameter, at: Vector3) -> f64 {
let d = p.definition_end_point - p.definition_base_point; let d = p.definition_end_point - p.definition_base_point;
let n = Vector3::new(-d.y, d.x, 0.) * (1.0 / d.x.hypot(d.y)); let n = Vector3::new(-d.y, d.x, 0.) * (1.0 / d.x.hypot(d.y));
+1 -1
View File
@@ -50,7 +50,7 @@ pub(crate) fn valid_lists(g: &BlockEvaluationGraph, node: i32) -> bool {
true true
} }
fn remove(g: &mut BlockEvaluationGraph, id: i32) { pub(crate) fn remove(g: &mut BlockEvaluationGraph, id: i32) {
let edge = g.edges.iter().find(|e| e.id == id).unwrap().clone(); let edge = g.edges.iter().find(|e| e.id == id).unwrap().clone();
let [pi, ni, po, no, _] = edge.outgoing_edges; let [pi, ni, po, no, _] = edge.outgoing_edges;
for (neighbor, field, value) in [(pi, 1, ni), (ni, 0, pi), (po, 3, no), (no, 2, po)] { for (neighbor, field, value) in [(pi, 1, ni), (ni, 0, pi), (po, 3, no), (no, 2, po)] {