forked from 1b-agent/opencadstudio-upstream
fix(block-editor): manage parameter and basepoint graph items
This commit is contained in:
1 parent
4fdf357a7e
commit
286bdf6426
2 files changed
+163
-14
No files matched your search
@@ -93,12 +93,31 @@ pub fn items(doc: &CadDocument, definition: Handle) -> Vec<Item> {
|
||||
p.parameter.definition_point,
|
||||
));
|
||||
}
|
||||
if let Some((handle, p)) = crate::scene::block_basepoint::parameter(doc, definition) {
|
||||
result.push(single(
|
||||
let basepoint = crate::scene::block_basepoint::parameter(doc, definition)
|
||||
.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,
|
||||
"Basepoint".into(),
|
||||
p.parameter.definition_point,
|
||||
));
|
||||
name: "Basepoint".into(),
|
||||
graphic,
|
||||
grips: vec![(Part::Point, point)],
|
||||
});
|
||||
}
|
||||
if let Some(p) = crate::scene::block_visibility::parameter(doc, definition) {
|
||||
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 {
|
||||
return Ok(false);
|
||||
}
|
||||
let graph = graph(doc, definition)?.ok_or("The authoring graph is unavailable.")?;
|
||||
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)?;
|
||||
*doc = staged;
|
||||
return Ok(true);
|
||||
}
|
||||
let graph = graph(doc, definition)?.ok_or("The authoring graph is unavailable.")?;
|
||||
let mut moved_grips = vec![];
|
||||
match staged
|
||||
.objects
|
||||
@@ -287,12 +309,8 @@ pub fn move_part(
|
||||
p.definition_label_point = at
|
||||
}
|
||||
}
|
||||
DynamicBlockData::MoveAction(p) => {
|
||||
p.action.display_location = at
|
||||
}
|
||||
DynamicBlockData::ArrayAction(p) => {
|
||||
p.action.display_location = at
|
||||
}
|
||||
DynamicBlockData::MoveAction(p) => p.action.display_location = at,
|
||||
DynamicBlockData::ArrayAction(p) => p.action.display_location = at,
|
||||
DynamicBlockData::StretchAction(p) => p.action.display_location = at,
|
||||
DynamicBlockData::FlipAction(p) => p.action.display_location = at,
|
||||
DynamicBlockData::ScaleAction(p) | DynamicBlockData::RotateAction(p) => {
|
||||
@@ -412,6 +430,137 @@ pub fn move_part(
|
||||
*doc = staged;
|
||||
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 {
|
||||
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));
|
||||
|
||||
@@ -50,7 +50,7 @@ pub(crate) fn valid_lists(g: &BlockEvaluationGraph, node: i32) -> bool {
|
||||
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 [pi, ni, po, no, _] = edge.outgoing_edges;
|
||||
for (neighbor, field, value) in [(pi, 1, ni), (ni, 0, pi), (po, 3, no), (no, 2, po)] {
|
||||
|
||||
Reference in new issue
Block a user