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 column: BlockLookupColumn,
|
||||||
pub value: String,
|
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> {
|
pub(crate) fn properties(document: &CadDocument, definition: Handle) -> Vec<Property> {
|
||||||
super::block_distance_parameter::parameters(document, definition)
|
super::block_distance_parameter::parameters(document, definition)
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|p| {
|
.flat_map(|p| {
|
||||||
matches!(
|
let node_id = p.native.parameter.element.eval.node_id;
|
||||||
p.kind,
|
let distance = (p.native.definition_end_point - p.native.definition_base_point)
|
||||||
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)
|
|
||||||
.length()
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -226,17 +240,21 @@ fn reference_row(
|
|||||||
if property.lookup_property {
|
if property.lookup_property {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if property.value_type != 40
|
if property.value_type != 40 {
|
||||||
|| !property
|
|
||||||
.connection_name
|
|
||||||
.eq_ignore_ascii_case("UpdatedDistance")
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let Some(parameter) = params.iter().find(|p| p.id == property.node_id) else {
|
let Some(parameter) = params.iter().find(|p| p.id == property.node_id) else {
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
let Ok(endpoint) = super::block_actions::instance::current(document, insert, parameter)
|
let Ok(endpoint) = super::block_actions::instance::current(document, insert, parameter)
|
||||||
@@ -250,10 +268,22 @@ fn reference_row(
|
|||||||
else {
|
else {
|
||||||
return false;
|
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()
|
actual.is_finite()
|
||||||
&& expected.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 parameters = super::block_actions::all_parameters(document, definition);
|
||||||
let mut evaluated = document.clone();
|
let mut evaluated = document.clone();
|
||||||
|
let mut endpoints = std::collections::BTreeMap::new();
|
||||||
for (column, property) in table.columns.iter().enumerate() {
|
for (column, property) in table.columns.iter().enumerate() {
|
||||||
if property.lookup_property {
|
if property.lookup_property {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if property.value_type != 40
|
if property.value_type != 40 {
|
||||||
|| !property
|
|
||||||
.connection_name
|
|
||||||
.eq_ignore_ascii_case("UpdatedDistance")
|
|
||||||
{
|
|
||||||
return Err("This Lookup input property is unsupported.");
|
return Err("This Lookup input property is unsupported.");
|
||||||
}
|
}
|
||||||
let parameter = parameters
|
let parameter = parameters
|
||||||
.iter()
|
.iter()
|
||||||
.find(|parameter| parameter.id == property.node_id)
|
.find(|parameter| parameter.id == property.node_id)
|
||||||
.ok_or("A Lookup input property is unavailable.")?;
|
.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.");
|
return Err("This Lookup input property is unsupported.");
|
||||||
}
|
}
|
||||||
let distance = table
|
let value = table
|
||||||
.expressions
|
.expressions
|
||||||
.get(row * columns + column)
|
.get(row * columns + column)
|
||||||
.ok_or("The selected Lookup row is incomplete.")?
|
.ok_or("The selected Lookup row is incomplete.")?
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<f64>()
|
.parse::<f64>()
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|value| value.is_finite() && *value > 1e-9)
|
.filter(|value| value.is_finite() && (!is_distance || *value > 1e-9))
|
||||||
.ok_or("The selected Lookup row has an invalid distance.")?;
|
.ok_or("The selected Lookup row has an invalid parameter value.")?;
|
||||||
let axis = (parameter.end - parameter.base)
|
let endpoint = if let Some(endpoint) = endpoints.get_mut(¶meter.id) {
|
||||||
.try_normalize()
|
endpoint
|
||||||
.ok_or("A Lookup input parameter has no direction.")?;
|
} else {
|
||||||
super::block_actions::set_value(
|
let current = super::block_actions::instance::current(document, insert, parameter)?;
|
||||||
&mut evaluated,
|
endpoints.entry(parameter.id).or_insert(current)
|
||||||
parameter,
|
};
|
||||||
super::block_actions::Value::Point(parameter.base + axis * distance),
|
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)?;
|
super::block_actions::rebuild(&mut evaluated, document, definition)?;
|
||||||
let mut result =
|
let mut result =
|
||||||
|
|||||||
@@ -301,6 +301,76 @@ fn lookup_reverse_selection_evaluates_saved_reference_geometry() {
|
|||||||
assert_eq!(unsupported, before);
|
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]
|
#[test]
|
||||||
fn lookup_existing_expressions_survive_row_reordering_and_output_edits() {
|
fn lookup_existing_expressions_survive_row_reordering_and_output_edits() {
|
||||||
let (mut document, definition) = fixture();
|
let (mut document, definition) = fixture();
|
||||||
|
|||||||
Reference in new issue
Block a user