forked from 1b-agent/opencadstudio-upstream
feat: evaluate polar Lookup angle inputs
Map UpdatedAngle columns to polar parameter endpoints and verify row-driven geometry materialization.
This commit is contained in:
1 parent
68f98ef720
commit
5c931e926b
2 files changed
+166
-45
No files matched your search
+96
-45
@@ -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(¶meter.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 ¶meters {
|
||||
if let Some(endpoint) = endpoints.get(¶meter.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 =
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user