feat: evaluate polar Lookup angle inputs

Map UpdatedAngle columns to polar parameter endpoints and verify row-driven geometry materialization.
This commit is contained in:
Karim Jerbi committed 2026-10-18 00:13:33 +01:00
1 parent 68f98ef720
commit 5c931e926b
2 files changed
+166 -45

No files matched your search

+96 -45
View File
@@ -154,30 +154,44 @@ pub(crate) struct Property {
pub column: BlockLookupColumn,
pub value: String,
}
/// These distance bindings are verified against the supplied native tables.
/// These distance and angle bindings match the native Lookup connection names.
pub(crate) fn properties(document: &CadDocument, definition: Handle) -> Vec<Property> {
super::block_distance_parameter::parameters(document, definition)
.iter()
.filter(|p| {
matches!(
p.kind,
super::block_distance_parameter::Kind::Linear
| super::block_distance_parameter::Kind::Polar
)
})
.map(|p| Property {
label: p.labels[0].clone(),
column: BlockLookupColumn {
node_id: p.native.parameter.element.eval.node_id,
value_type: 40,
property_type: 2,
connection_name: "UpdatedDistance".into(),
unmatched_name: "Custom".into(),
..Default::default()
},
value: (p.native.definition_end_point - p.native.definition_base_point)
.flat_map(|p| {
let node_id = p.native.parameter.element.eval.node_id;
let distance = (p.native.definition_end_point - p.native.definition_base_point)
.length()
.to_string(),
.to_string();
let mut properties = vec![Property {
label: p.labels[0].clone(),
column: BlockLookupColumn {
node_id,
value_type: 40,
property_type: 2,
connection_name: "UpdatedDistance".into(),
unmatched_name: "Custom".into(),
..Default::default()
},
value: distance,
}];
if p.kind == super::block_distance_parameter::Kind::Polar {
let delta = p.native.definition_end_point - p.native.definition_base_point;
let angle = delta.y.atan2(delta.x).rem_euclid(std::f64::consts::TAU);
properties.push(Property {
label: p.labels[1].clone(),
column: BlockLookupColumn {
node_id,
value_type: 40,
property_type: 2,
connection_name: "UpdatedAngle".into(),
unmatched_name: "Custom".into(),
..Default::default()
},
value: angle.to_string(),
});
}
properties
})
.collect()
}
@@ -226,17 +240,21 @@ fn reference_row(
if property.lookup_property {
return true;
}
if property.value_type != 40
|| !property
.connection_name
.eq_ignore_ascii_case("UpdatedDistance")
{
if property.value_type != 40 {
return false;
}
let Some(parameter) = params.iter().find(|p| p.id == property.node_id) else {
return false;
};
if !(parameter.linear || parameter.polar) {
let is_distance = property
.connection_name
.eq_ignore_ascii_case("UpdatedDistance");
let is_angle = property
.connection_name
.eq_ignore_ascii_case("UpdatedAngle");
if !(is_distance && (parameter.linear || parameter.polar)
|| is_angle && parameter.polar)
{
return false;
}
let Ok(endpoint) = super::block_actions::instance::current(document, insert, parameter)
@@ -250,10 +268,22 @@ fn reference_row(
else {
return false;
};
let actual = endpoint.distance(parameter.base);
let actual = if is_angle {
(endpoint.y - parameter.base.y)
.atan2(endpoint.x - parameter.base.x)
.rem_euclid(std::f64::consts::TAU)
} else {
endpoint.distance(parameter.base)
};
let difference = if is_angle {
(actual - expected + std::f64::consts::PI).rem_euclid(std::f64::consts::TAU)
- std::f64::consts::PI
} else {
actual - expected
};
actual.is_finite()
&& expected.is_finite()
&& (actual - expected).abs() <= 1e-8 * actual.abs().max(expected.abs()).max(1.0)
&& difference.abs() <= 1e-8 * actual.abs().max(expected.abs()).max(1.0)
})
})
}
@@ -334,41 +364,62 @@ pub(crate) fn evaluate_reference_value(
let parameters = super::block_actions::all_parameters(document, definition);
let mut evaluated = document.clone();
let mut endpoints = std::collections::BTreeMap::new();
for (column, property) in table.columns.iter().enumerate() {
if property.lookup_property {
continue;
}
if property.value_type != 40
|| !property
.connection_name
.eq_ignore_ascii_case("UpdatedDistance")
{
if property.value_type != 40 {
return Err("This Lookup input property is unsupported.");
}
let parameter = parameters
.iter()
.find(|parameter| parameter.id == property.node_id)
.ok_or("A Lookup input property is unavailable.")?;
if !(parameter.linear || parameter.polar) {
let is_distance = property
.connection_name
.eq_ignore_ascii_case("UpdatedDistance");
let is_angle = property
.connection_name
.eq_ignore_ascii_case("UpdatedAngle");
if !(is_distance && (parameter.linear || parameter.polar) || is_angle && parameter.polar) {
return Err("This Lookup input property is unsupported.");
}
let distance = table
let value = table
.expressions
.get(row * columns + column)
.ok_or("The selected Lookup row is incomplete.")?
.trim()
.parse::<f64>()
.ok()
.filter(|value| value.is_finite() && *value > 1e-9)
.ok_or("The selected Lookup row has an invalid distance.")?;
let axis = (parameter.end - parameter.base)
.try_normalize()
.ok_or("A Lookup input parameter has no direction.")?;
super::block_actions::set_value(
&mut evaluated,
parameter,
super::block_actions::Value::Point(parameter.base + axis * distance),
)?;
.filter(|value| value.is_finite() && (!is_distance || *value > 1e-9))
.ok_or("The selected Lookup row has an invalid parameter value.")?;
let endpoint = if let Some(endpoint) = endpoints.get_mut(&parameter.id) {
endpoint
} else {
let current = super::block_actions::instance::current(document, insert, parameter)?;
endpoints.entry(parameter.id).or_insert(current)
};
let delta = *endpoint - parameter.base;
if is_distance {
let axis = delta
.try_normalize()
.or_else(|| (parameter.end - parameter.base).try_normalize())
.ok_or("A Lookup input parameter has no direction.")?;
*endpoint = parameter.base + axis * value;
} else {
let radius = delta.length();
*endpoint = parameter.base + glam::DVec3::new(value.cos(), value.sin(), 0.0) * radius;
}
}
for parameter in &parameters {
if let Some(endpoint) = endpoints.get(&parameter.id) {
super::block_actions::set_value(
&mut evaluated,
parameter,
super::block_actions::Value::Point(*endpoint),
)?;
}
}
super::block_actions::rebuild(&mut evaluated, document, definition)?;
let mut result =
+70
View File
@@ -301,6 +301,76 @@ fn lookup_reverse_selection_evaluates_saved_reference_geometry() {
assert_eq!(unsupported, before);
}
#[test]
fn lookup_updated_angle_rotates_polar_parameter_and_materializes_action_geometry() {
let (mut document, definition) = fixture();
let line = document.block_records.get("LOOKUP").unwrap().entity_handles[0];
let parameter = distance::create(
&mut document,
definition,
distance::Kind::Polar,
Vector3::ZERO,
Vector3::new(2.0, 0.0, 0.0),
Vector3::UNIT_Y,
)
.unwrap();
crate::scene::block_actions::create(
&mut document,
definition,
crate::scene::block_actions::Kind::Move,
parameter,
&[line],
glam::DVec3::ZERO,
)
.unwrap();
let lookup = create(&mut document, definition, Vector3::ZERO).unwrap();
let lookup_parameter = parameters(&document, definition)
.into_iter()
.find(|(handle, _)| *handle == lookup)
.unwrap()
.1;
let polar_properties = properties(&document, definition);
assert_eq!(polar_properties.len(), 2);
assert_eq!(polar_properties[1].column.connection_name, "UpdatedAngle");
let mut table = empty_table(lookup_parameter);
table.columns.insert(0, polar_properties[0].column.clone());
table.columns.insert(1, polar_properties[1].column.clone());
table.column_count = 3;
table.row_count = 2;
table.expressions = vec![
"2".into(),
"0".into(),
"Small".into(),
"4".into(),
std::f64::consts::FRAC_PI_2.to_string(),
"Large".into(),
];
apply_table(&mut document, definition, lookup, table).unwrap();
let insert = document
.add_entity(EntityType::Insert(Insert::new("LOOKUP", Vector3::ZERO)))
.unwrap();
let evaluated = evaluate_reference_choice(&document, insert, "Large")
.unwrap()
.unwrap();
let EntityType::Insert(reference) = evaluated.get_entity(insert).unwrap() else {
panic!()
};
let member = evaluated
.block_records
.get(&reference.block_name)
.unwrap()
.entity_handles[0];
let EntityType::Line(line) = evaluated.get_entity(member).unwrap() else {
panic!()
};
assert!((line.start - Vector3::new(-2.0, 4.0, 0.0)).length() < 1e-9);
assert!((line.end - Vector3::new(-1.0, 4.0, 0.0)).length() < 1e-9);
assert!(evaluate_reference_choice(&evaluated, insert, "Large")
.unwrap()
.is_none());
}
#[test]
fn lookup_existing_expressions_survive_row_reordering_and_output_edits() {
let (mut document, definition) = fixture();