forked from 1b-agent/opencadstudio-upstream
feat(bpt): author native table grips and must-match
This commit is contained in:
1 parent
b6fbac39ff
commit
d3e64800f6
4 files changed
+299
-66
No files matched your search
@@ -7,7 +7,9 @@ impl OpenCADStudio {
|
|||||||
pub(in crate::app) fn properties_table_error(&self) -> Option<&'static str> {
|
pub(in crate::app) fn properties_table_error(&self) -> Option<&'static str> {
|
||||||
let tab = &self.tabs[self.active_tab];
|
let tab = &self.tabs[self.active_tab];
|
||||||
let Some(session) = tab.active_block_edit_session() else {
|
let Some(session) = tab.active_block_edit_session() else {
|
||||||
return Some("Open a block in the Block Editor before editing its Block Properties Table.");
|
return Some(
|
||||||
|
"Open a block in the Block Editor before editing its Block Properties Table.",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
let definition = session.br_handle;
|
let definition = session.br_handle;
|
||||||
if table::columns(&tab.scene.document, definition).is_empty() {
|
if table::columns(&tab.scene.document, definition).is_empty() {
|
||||||
@@ -39,11 +41,13 @@ impl OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let must_match = original.as_ref().is_some_and(|view| view.must_match);
|
||||||
self.block_properties_table = Some(State {
|
self.block_properties_table = Some(State {
|
||||||
document: tab.id,
|
document: tab.id,
|
||||||
definition,
|
definition,
|
||||||
epoch: tab.scene.geometry_epoch,
|
epoch: tab.scene.geometry_epoch,
|
||||||
original,
|
original,
|
||||||
|
must_match,
|
||||||
columns,
|
columns,
|
||||||
properties: table::columns(&tab.scene.document, definition),
|
properties: table::columns(&tab.scene.document, definition),
|
||||||
selected_property: None,
|
selected_property: None,
|
||||||
@@ -71,9 +75,8 @@ impl OpenCADStudio {
|
|||||||
match value.trim().parse::<f64>() {
|
match value.trim().parse::<f64>() {
|
||||||
Ok(value) if value.is_finite() => parsed.push(value),
|
Ok(value) if value.is_finite() => parsed.push(value),
|
||||||
_ => {
|
_ => {
|
||||||
self.block_properties_table.as_mut().unwrap().error = Some(
|
self.block_properties_table.as_mut().unwrap().error =
|
||||||
crate::t!("Each cell must be a finite number.").into_owned(),
|
Some(crate::t!("Each cell must be a finite number.").into_owned());
|
||||||
);
|
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,6 +103,7 @@ impl OpenCADStudio {
|
|||||||
let unchanged = state.original.as_ref().is_some_and(|view| {
|
let unchanged = state.original.as_ref().is_some_and(|view| {
|
||||||
view.columns == state.columns
|
view.columns == state.columns
|
||||||
&& view.rows.iter().eq(cells.iter())
|
&& view.rows.iter().eq(cells.iter())
|
||||||
|
&& view.must_match == state.must_match
|
||||||
});
|
});
|
||||||
if unchanged {
|
if unchanged {
|
||||||
self.block_properties_table = None;
|
self.block_properties_table = None;
|
||||||
@@ -107,9 +111,13 @@ impl OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
let mut working = tab.scene.document.clone();
|
let mut working = tab.scene.document.clone();
|
||||||
if let Err(error) =
|
if let Err(error) = table::apply(
|
||||||
table::apply(&mut working, state.definition, &state.columns, &cells)
|
&mut working,
|
||||||
{
|
state.definition,
|
||||||
|
&state.columns,
|
||||||
|
&cells,
|
||||||
|
state.must_match,
|
||||||
|
) {
|
||||||
self.block_properties_table.as_mut().unwrap().error =
|
self.block_properties_table.as_mut().unwrap().error =
|
||||||
Some(crate::t!(error).into_owned());
|
Some(crate::t!(error).into_owned());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|||||||
@@ -452,7 +452,9 @@ pub(crate) fn create_with_options(
|
|||||||
.get_entity(*h)
|
.get_entity(*h)
|
||||||
.is_none_or(|e| !supported(e) || e.common().owner_handle != definition)
|
.is_none_or(|e| !supported(e) || e.common().owner_handle != definition)
|
||||||
}) {
|
}) {
|
||||||
return Err("This milestone supports lines, polylines, arcs, circles, ellipses, points and text belonging to this block.");
|
return Err(
|
||||||
|
"This milestone supports lines, polylines, arcs, circles, ellipses, points and text belonging to this block.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let (column_offset, row_offset) = if kind == Kind::Array {
|
let (column_offset, row_offset) = if kind == Kind::Array {
|
||||||
let (col, row) = array_offsets.unwrap_or((1.0, 1.0));
|
let (col, row) = array_offsets.unwrap_or((1.0, 1.0));
|
||||||
@@ -1034,14 +1036,81 @@ fn evaluate_impl(
|
|||||||
if length < 1e-9 {
|
if length < 1e-9 {
|
||||||
return Err("Specify a positive parameter length.");
|
return Err("Specify a positive parameter length.");
|
||||||
}
|
}
|
||||||
position = p.base + axis * length;
|
let mut snapped_length = length;
|
||||||
|
if !force {
|
||||||
|
if let Some(table) = super::block_properties_table::table(baseline, definition)?
|
||||||
|
{
|
||||||
|
if table.must_match
|
||||||
|
&& table.columns.iter().any(|column| {
|
||||||
|
column.handle == p.handle
|
||||||
|
&& column
|
||||||
|
.connection_name
|
||||||
|
.eq_ignore_ascii_case("UpdatedDistance")
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let values = table
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.position(|column| {
|
||||||
|
column.handle == p.handle
|
||||||
|
&& column
|
||||||
|
.connection_name
|
||||||
|
.eq_ignore_ascii_case("UpdatedDistance")
|
||||||
|
})
|
||||||
|
.map(|i| {
|
||||||
|
table
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| r.get(i).copied())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
if let Some(nearest) = values.iter().copied().min_by(|a, b| {
|
||||||
|
(a - length)
|
||||||
|
.abs()
|
||||||
|
.total_cmp(&(b - length).abs())
|
||||||
|
.then_with(|| a.total_cmp(b))
|
||||||
|
}) {
|
||||||
|
snapped_length = nearest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position = p.base + axis * snapped_length;
|
||||||
}
|
}
|
||||||
if p.rotation {
|
if p.rotation {
|
||||||
let delta = position - p.base;
|
let delta = position - p.base;
|
||||||
if delta.length() < 1e-9 {
|
if delta.length() < 1e-9 {
|
||||||
return Err("Specify a rotation direction away from the parameter base.");
|
return Err("Specify a rotation direction away from the parameter base.");
|
||||||
}
|
}
|
||||||
position = p.base + delta.normalize() * p.end.distance(p.base);
|
let mut angle = delta.y.atan2(delta.x);
|
||||||
|
if !force {
|
||||||
|
if let Some(table) = super::block_properties_table::table(baseline, definition)?
|
||||||
|
{
|
||||||
|
if table.must_match {
|
||||||
|
if let Some(col) = table.columns.iter().position(|column| {
|
||||||
|
column.handle == p.handle
|
||||||
|
&& column.connection_name.eq_ignore_ascii_case("UpdatedAngle")
|
||||||
|
}) {
|
||||||
|
if let Some(nearest) = table
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.filter_map(|row| row.get(col).copied())
|
||||||
|
.min_by(|a, b| {
|
||||||
|
(a - angle)
|
||||||
|
.abs()
|
||||||
|
.total_cmp(&(b - angle).abs())
|
||||||
|
.then_with(|| a.total_cmp(b))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
angle = nearest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position =
|
||||||
|
p.base + DVec3::new(angle.cos(), angle.sin(), 0.0) * p.end.distance(p.base);
|
||||||
}
|
}
|
||||||
Value::Point(position)
|
Value::Point(position)
|
||||||
}
|
}
|
||||||
@@ -1203,7 +1272,7 @@ fn evaluate_impl(
|
|||||||
]
|
]
|
||||||
} =>
|
} =>
|
||||||
{
|
{
|
||||||
return Err("This Move action uses options not supported yet.")
|
return Err("This Move action uses options not supported yet.");
|
||||||
}
|
}
|
||||||
DynamicBlockData::FlipAction(f)
|
DynamicBlockData::FlipAction(f)
|
||||||
if f.connections
|
if f.connections
|
||||||
@@ -1214,7 +1283,7 @@ fn evaluate_impl(
|
|||||||
}
|
}
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
return Err("This Flip action uses options not supported yet.")
|
return Err("This Flip action uses options not supported yet.");
|
||||||
}
|
}
|
||||||
DynamicBlockData::RotateAction(r)
|
DynamicBlockData::RotateAction(r)
|
||||||
if !r.action.dependent
|
if !r.action.dependent
|
||||||
@@ -1230,7 +1299,7 @@ fn evaluate_impl(
|
|||||||
name: name.into(),
|
name: name.into(),
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
return Err("This Rotate action uses options not supported yet.")
|
return Err("This Rotate action uses options not supported yet.");
|
||||||
}
|
}
|
||||||
DynamicBlockData::ScaleAction(s)
|
DynamicBlockData::ScaleAction(s)
|
||||||
if !s.action.dependent
|
if !s.action.dependent
|
||||||
@@ -1246,7 +1315,7 @@ fn evaluate_impl(
|
|||||||
name: name.into(),
|
name: name.into(),
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
return Err("This Scale action uses options not supported yet.")
|
return Err("This Scale action uses options not supported yet.");
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
//! Native Block Properties Table authoring.
|
//! Native Block Properties Table authoring.
|
||||||
use super::block_authoring::{self as authoring, *};
|
use super::block_authoring::{self as authoring, *};
|
||||||
use codec::objects::{
|
use codec::objects::{
|
||||||
BlockElement, BlockEvalValue, BlockOnePointParameter, BlockParameter, BlockPropertiesTable,
|
BlockElement, BlockEvalValue, BlockGrip, BlockGripExpression, BlockOnePointParameter,
|
||||||
BlockPropertiesTableField, BlockPropertiesTableValue, DynamicBlockData, ObjectType,
|
BlockParameter, BlockPropertiesTable, BlockPropertiesTableField, BlockPropertiesTableValue,
|
||||||
|
DynamicBlockData, ObjectType,
|
||||||
};
|
};
|
||||||
use codec::{CadDocument, Handle};
|
use codec::{CadDocument, Handle};
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ pub(crate) struct Column {
|
|||||||
pub handle: Handle,
|
pub handle: Handle,
|
||||||
pub node_id: i32,
|
pub node_id: i32,
|
||||||
pub connection_name: String,
|
pub connection_name: String,
|
||||||
|
pub display_name: String,
|
||||||
pub type_code: i64,
|
pub type_code: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +24,12 @@ pub(crate) struct TableView {
|
|||||||
pub rows: Vec<Vec<f64>>,
|
pub rows: Vec<Vec<f64>>,
|
||||||
pub row_start: usize,
|
pub row_start: usize,
|
||||||
pub trailer_start: usize,
|
pub trailer_start: usize,
|
||||||
|
pub must_match: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type Grid = (Handle, Vec<Column>, Vec<Vec<String>>);
|
pub(crate) type Grid = (Handle, Vec<Column>, Vec<Vec<String>>);
|
||||||
|
|
||||||
fn graph_handle(document: &CadDocument, definition: Handle) -> Option<Handle> {
|
pub(crate) fn graph_handle(document: &CadDocument, definition: Handle) -> Option<Handle> {
|
||||||
let xdict = document.extension_dictionary_handle(definition)?;
|
let xdict = document.extension_dictionary_handle(definition)?;
|
||||||
let ObjectType::Dictionary(dictionary) = document.objects.get(&xdict)? else {
|
let ObjectType::Dictionary(dictionary) = document.objects.get(&xdict)? else {
|
||||||
return None;
|
return None;
|
||||||
@@ -59,6 +62,20 @@ pub(crate) fn table(
|
|||||||
.find(|parameter| parameter.handle == column.handle)
|
.find(|parameter| parameter.handle == column.handle)
|
||||||
.map(|parameter| parameter.id)
|
.map(|parameter| parameter.id)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
if column.node_id != 0 {
|
||||||
|
column.type_code = i64::from(column.node_id);
|
||||||
|
}
|
||||||
|
column.display_name = super::block_lookup::properties(document, definition)
|
||||||
|
.into_iter()
|
||||||
|
.find(|property| {
|
||||||
|
property.column.node_id == column.node_id
|
||||||
|
&& property
|
||||||
|
.column
|
||||||
|
.connection_name
|
||||||
|
.eq_ignore_ascii_case(&column.connection_name)
|
||||||
|
})
|
||||||
|
.map(|property| property.label)
|
||||||
|
.unwrap_or_else(|| column.connection_name.clone());
|
||||||
}
|
}
|
||||||
Ok(Some(view))
|
Ok(Some(view))
|
||||||
}
|
}
|
||||||
@@ -90,9 +107,10 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|field| field.code == 91)
|
.position(|field| field.code == 91)
|
||||||
.ok_or("The Block Properties Table has no property columns.")?;
|
.ok_or("The Block Properties Table has no property columns.")?;
|
||||||
let column_count = usize::try_from(integer(&fields[column_count_index]).ok_or(
|
let column_count = usize::try_from(
|
||||||
"The Block Properties Table column count is invalid.",
|
integer(&fields[column_count_index])
|
||||||
)?)
|
.ok_or("The Block Properties Table column count is invalid.")?,
|
||||||
|
)
|
||||||
.map_err(|_| "The Block Properties Table column count is invalid.")?;
|
.map_err(|_| "The Block Properties Table column count is invalid.")?;
|
||||||
if column_count == 0 || column_count > 64 {
|
if column_count == 0 || column_count > 64 {
|
||||||
return Err("The Block Properties Table column count is unsupported.");
|
return Err("The Block Properties Table column count is unsupported.");
|
||||||
@@ -135,7 +153,8 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
|
|||||||
columns.push(Column {
|
columns.push(Column {
|
||||||
handle: column_handle,
|
handle: column_handle,
|
||||||
node_id: 0,
|
node_id: 0,
|
||||||
connection_name: name,
|
connection_name: name.clone(),
|
||||||
|
display_name: name,
|
||||||
type_code: property_id,
|
type_code: property_id,
|
||||||
});
|
});
|
||||||
index = end + 1;
|
index = end + 1;
|
||||||
@@ -147,9 +166,10 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
|
|||||||
.find(|(_, field)| field.code == 92)
|
.find(|(_, field)| field.code == 92)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.ok_or("The Block Properties Table row data is unavailable.")?;
|
.ok_or("The Block Properties Table row data is unavailable.")?;
|
||||||
let row_count = usize::try_from(integer(&fields[row_count_index]).ok_or(
|
let row_count = usize::try_from(
|
||||||
"The Block Properties Table row count is invalid.",
|
integer(&fields[row_count_index])
|
||||||
)?)
|
.ok_or("The Block Properties Table row count is invalid.")?,
|
||||||
|
)
|
||||||
.map_err(|_| "The Block Properties Table row count is invalid.")?;
|
.map_err(|_| "The Block Properties Table row count is invalid.")?;
|
||||||
if row_count == 0 || row_count > 1000 {
|
if row_count == 0 || row_count > 1000 {
|
||||||
return Err("The Block Properties Table row count is unsupported.");
|
return Err("The Block Properties Table row count is unsupported.");
|
||||||
@@ -195,6 +215,13 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
|
|||||||
.find(|(_, field)| field.code == 93)
|
.find(|(_, field)| field.code == 93)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.ok_or("The Block Properties Table trailer is unavailable.")?;
|
.ok_or("The Block Properties Table trailer is unavailable.")?;
|
||||||
|
let must_match = fields
|
||||||
|
.get(trailer_start + 1)
|
||||||
|
.and_then(|f| match f.value {
|
||||||
|
BlockPropertiesTableValue::Boolean(v) if f.code == 290 => Some(v),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
Ok(TableView {
|
Ok(TableView {
|
||||||
handle: table_handle,
|
handle: table_handle,
|
||||||
table,
|
table,
|
||||||
@@ -202,6 +229,7 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
|
|||||||
rows,
|
rows,
|
||||||
row_start,
|
row_start,
|
||||||
trailer_start,
|
trailer_start,
|
||||||
|
must_match,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,29 +238,26 @@ pub(crate) fn columns(document: &CadDocument, definition: Handle) -> Vec<Column>
|
|||||||
super::block_lookup::properties(document, definition)
|
super::block_lookup::properties(document, definition)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|property| {
|
.filter_map(|property| {
|
||||||
let parameter = parameters.iter().find(|parameter| {
|
let parameter = parameters
|
||||||
parameter.id == property.column.node_id
|
.iter()
|
||||||
})?;
|
.find(|parameter| parameter.id == property.column.node_id)?;
|
||||||
let type_code = if property
|
if !property
|
||||||
.column
|
.column
|
||||||
.connection_name
|
.connection_name
|
||||||
.eq_ignore_ascii_case("UpdatedDistance")
|
.eq_ignore_ascii_case("UpdatedDistance")
|
||||||
|
&& !property
|
||||||
|
.column
|
||||||
|
.connection_name
|
||||||
|
.eq_ignore_ascii_case("UpdatedAngle")
|
||||||
{
|
{
|
||||||
14
|
|
||||||
} else if property
|
|
||||||
.column
|
|
||||||
.connection_name
|
|
||||||
.eq_ignore_ascii_case("UpdatedAngle")
|
|
||||||
{
|
|
||||||
21
|
|
||||||
} else {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
}
|
||||||
Some(Column {
|
Some(Column {
|
||||||
handle: parameter.handle,
|
handle: parameter.handle,
|
||||||
node_id: parameter.id,
|
node_id: parameter.id,
|
||||||
connection_name: property.column.connection_name,
|
connection_name: property.column.connection_name,
|
||||||
type_code,
|
display_name: property.label,
|
||||||
|
type_code: i64::from(parameter.id),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -258,24 +283,26 @@ pub(crate) fn apply(
|
|||||||
definition: Handle,
|
definition: Handle,
|
||||||
columns: &[Column],
|
columns: &[Column],
|
||||||
rows: &[Vec<f64>],
|
rows: &[Vec<f64>],
|
||||||
|
must_match: bool,
|
||||||
) -> Result<Handle, &'static str> {
|
) -> Result<Handle, &'static str> {
|
||||||
if columns.is_empty() || columns.len() > 64 || rows.is_empty() || rows.len() > 1000 {
|
if columns.is_empty() || columns.len() > 64 || rows.is_empty() || rows.len() > 1000 {
|
||||||
return Err("Add at least one property and one complete row.");
|
return Err("Add at least one property and one complete row.");
|
||||||
}
|
}
|
||||||
if rows.iter().any(|row| {
|
if rows
|
||||||
row.len() != columns.len() || row.iter().any(|value| !value.is_finite())
|
.iter()
|
||||||
}) {
|
.any(|row| row.len() != columns.len() || row.iter().any(|value| !value.is_finite()))
|
||||||
|
{
|
||||||
return Err("Complete each numeric property cell with a finite value.");
|
return Err("Complete each numeric property cell with a finite value.");
|
||||||
}
|
}
|
||||||
let mut working = document.clone();
|
let mut working = document.clone();
|
||||||
let graph = graph_handle(&working, definition).ok_or("The authoring graph is unavailable.")?;
|
graph_handle(&working, definition).ok_or("The authoring graph is unavailable.")?;
|
||||||
let previous = table(&working, definition)?;
|
let previous = table(&working, definition)?;
|
||||||
|
let mut new_table_eval = None;
|
||||||
let handle = if let Some(view) = &previous {
|
let handle = if let Some(view) = &previous {
|
||||||
view.handle
|
view.handle
|
||||||
} else {
|
} else {
|
||||||
let (graph, slots) = authoring::prepare(&mut working, definition).map_err(|_| {
|
let (graph, slots) = authoring::prepare(&mut working, definition)
|
||||||
"The block authoring graph cannot accept a Block Properties Table."
|
.map_err(|_| "The block authoring graph cannot accept a Block Properties Table.")?;
|
||||||
})?;
|
|
||||||
register_classes(
|
register_classes(
|
||||||
&mut working,
|
&mut working,
|
||||||
&[("BLOCKPROPERTIESTABLE", "AcDbBlockPropertiesTable", 1153)],
|
&[("BLOCKPROPERTIESTABLE", "AcDbBlockPropertiesTable", 1153)],
|
||||||
@@ -288,6 +315,7 @@ pub(crate) fn apply(
|
|||||||
minor: MINOR,
|
minor: MINOR,
|
||||||
eed_1071: 0,
|
eed_1071: 0,
|
||||||
};
|
};
|
||||||
|
new_table_eval = Some(slots.eval);
|
||||||
let parameter = BlockOnePointParameter {
|
let parameter = BlockOnePointParameter {
|
||||||
parameter: BlockParameter {
|
parameter: BlockParameter {
|
||||||
element,
|
element,
|
||||||
@@ -315,7 +343,7 @@ pub(crate) fn apply(
|
|||||||
handle
|
handle
|
||||||
};
|
};
|
||||||
let old_fields = previous.as_ref().map(|view| view.table.fields.as_slice());
|
let old_fields = previous.as_ref().map(|view| view.table.fields.as_slice());
|
||||||
let new_fields = encode_fields(columns, rows, old_fields)?;
|
let new_fields = encode_fields(columns, rows, old_fields, must_match)?;
|
||||||
let Some(ObjectType::DynamicBlock(object)) = working.objects.get_mut(&handle) else {
|
let Some(ObjectType::DynamicBlock(object)) = working.objects.get_mut(&handle) else {
|
||||||
return Err("The Block Properties Table object is unavailable.");
|
return Err("The Block Properties Table object is unavailable.");
|
||||||
};
|
};
|
||||||
@@ -323,24 +351,123 @@ pub(crate) fn apply(
|
|||||||
return Err("The selected object is not a Block Properties Table.");
|
return Err("The selected object is not a Block Properties Table.");
|
||||||
};
|
};
|
||||||
table.fields = new_fields;
|
table.fields = new_fields;
|
||||||
|
if let Some(eval) = new_table_eval {
|
||||||
|
table.parameter.property_count = eval + 1;
|
||||||
|
}
|
||||||
|
let graph = graph_handle(&working, definition).ok_or("The authoring graph is unavailable.")?;
|
||||||
|
ensure_grip(&mut working, definition, graph, handle)?;
|
||||||
let _ = graph;
|
let _ = graph;
|
||||||
*document = working;
|
*document = working;
|
||||||
Ok(handle)
|
Ok(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_grip(
|
||||||
|
document: &mut CadDocument,
|
||||||
|
definition: Handle,
|
||||||
|
graph: Handle,
|
||||||
|
table_handle: Handle,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
let already = document.objects.values().any(|value| matches!(value, ObjectType::DynamicBlock(object) if object.owner == graph && matches!(&object.data, DynamicBlockData::PropertiesTableGrip(_))));
|
||||||
|
let Some(ObjectType::DynamicBlock(table_obj)) = document.objects.get(&table_handle) else {
|
||||||
|
return Err("The Block Properties Table object is unavailable.");
|
||||||
|
};
|
||||||
|
let DynamicBlockData::PropertiesTable(table_data) = &table_obj.data else {
|
||||||
|
return Err("The selected object is not a Block Properties Table.");
|
||||||
|
};
|
||||||
|
let location = table_data.parameter.definition_point;
|
||||||
|
let table_eval = table_data.parameter.parameter.element.eval.node_id;
|
||||||
|
if already {
|
||||||
|
let grip_eval = document.objects.values().find_map(|value| match value {
|
||||||
|
ObjectType::DynamicBlock(object) if object.owner == graph => match &object.data {
|
||||||
|
DynamicBlockData::PropertiesTableGrip(grip) => Some(grip.element.eval.node_id),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let Some(grip_eval) = grip_eval {
|
||||||
|
if let Some(ObjectType::DynamicBlock(object)) = document.objects.get_mut(&table_handle)
|
||||||
|
{
|
||||||
|
if let DynamicBlockData::PropertiesTable(table) = &mut object.data {
|
||||||
|
table.parameter.property_count = grip_eval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let (graph, slots) = authoring::prepare(document, definition)
|
||||||
|
.map_err(|_| "The block authoring graph cannot accept a table grip.")?;
|
||||||
|
authoring::register_classes(
|
||||||
|
document,
|
||||||
|
&[
|
||||||
|
("BLOCKPROPERTIESTABLEGRIP", "AcDbBlockGrip", 55),
|
||||||
|
("BLOCKGRIPLOCATIONCOMPONENT", "AcDbBlockGripExpr", 20),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let grip = document.allocate_handle();
|
||||||
|
let x = document.allocate_handle();
|
||||||
|
let y = document.allocate_handle();
|
||||||
|
let grip_data = BlockGrip {
|
||||||
|
element: BlockElement {
|
||||||
|
eval: expression(slots.eval, NO_VALUE, BlockEvalValue::None),
|
||||||
|
name: "Grip".into(),
|
||||||
|
major: MAJOR,
|
||||||
|
minor: MINOR,
|
||||||
|
eed_1071: 0,
|
||||||
|
},
|
||||||
|
flags_91: slots.eval + 1,
|
||||||
|
flags_92: slots.eval + 2,
|
||||||
|
location,
|
||||||
|
insert_cycling: false,
|
||||||
|
insert_cycling_weight: -1,
|
||||||
|
};
|
||||||
|
document.objects.insert(
|
||||||
|
grip,
|
||||||
|
ObjectType::DynamicBlock(dynamic(
|
||||||
|
grip,
|
||||||
|
graph,
|
||||||
|
"BLOCKPROPERTIESTABLEGRIP",
|
||||||
|
"AcDbBlockGrip",
|
||||||
|
DynamicBlockData::PropertiesTableGrip(grip_data),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
for (handle, eval, axis) in [
|
||||||
|
(x, slots.eval + 1, "UpdatedX"),
|
||||||
|
(y, slots.eval + 2, "UpdatedY"),
|
||||||
|
] {
|
||||||
|
document.objects.insert(
|
||||||
|
handle,
|
||||||
|
ObjectType::DynamicBlock(dynamic(
|
||||||
|
handle,
|
||||||
|
graph,
|
||||||
|
"BLOCKGRIPLOCATIONCOMPONENT",
|
||||||
|
"AcDbBlockGripExpr",
|
||||||
|
DynamicBlockData::GripLocationComponent(BlockGripExpression {
|
||||||
|
eval: expression(eval, 40, BlockEvalValue::Real(0.0)),
|
||||||
|
grip_type: table_eval,
|
||||||
|
expression: axis.into(),
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
authoring::append_components(document, graph, slots, &[grip, x, y], &[1, 2]);
|
||||||
|
if let Some(ObjectType::DynamicBlock(object)) = document.objects.get_mut(&table_handle) {
|
||||||
|
if let DynamicBlockData::PropertiesTable(table) = &mut object.data {
|
||||||
|
table.parameter.property_count = slots.eval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn encode_fields(
|
fn encode_fields(
|
||||||
columns: &[Column],
|
columns: &[Column],
|
||||||
rows: &[Vec<f64>],
|
rows: &[Vec<f64>],
|
||||||
old: Option<&[BlockPropertiesTableField]>,
|
old: Option<&[BlockPropertiesTableField]>,
|
||||||
|
must_match: bool,
|
||||||
) -> Result<Vec<BlockPropertiesTableField>, &'static str> {
|
) -> Result<Vec<BlockPropertiesTableField>, &'static str> {
|
||||||
let mut fields = if let Some(fields) = old {
|
let mut fields = if let Some(fields) = old {
|
||||||
fields.to_vec()
|
fields.to_vec()
|
||||||
} else {
|
} else {
|
||||||
vec![
|
vec![int(90, 2), text(300, "Block Table1"), text(301, "")]
|
||||||
int(90, 2),
|
|
||||||
text(300, "Block Properties Table"),
|
|
||||||
text(301, ""),
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
if old.is_none() {
|
if old.is_none() {
|
||||||
fields.push(int(91, columns.len() as i64));
|
fields.push(int(91, columns.len() as i64));
|
||||||
@@ -351,33 +478,55 @@ fn encode_fields(
|
|||||||
int(171, -1),
|
int(171, -1),
|
||||||
text(300, ""),
|
text(300, ""),
|
||||||
text(301, &column.connection_name),
|
text(301, &column.connection_name),
|
||||||
int(90, column.type_code),
|
int(90, column.node_id as i64),
|
||||||
int(170, -9999),
|
int(170, -9999),
|
||||||
int(170, -9999),
|
int(170, -9999),
|
||||||
boolean(290, false),
|
boolean(290, false),
|
||||||
boolean(291, true),
|
boolean(291, true),
|
||||||
boolean(292, true),
|
boolean(292, true),
|
||||||
boolean(293, column.type_code == 21),
|
boolean(
|
||||||
|
293,
|
||||||
|
column.connection_name.eq_ignore_ascii_case("UpdatedAngle"),
|
||||||
|
),
|
||||||
boolean(294, false),
|
boolean(294, false),
|
||||||
text(302, ""),
|
text(302, ""),
|
||||||
handle_field(340, Handle::NULL),
|
handle_field(340, Handle::NULL),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let view = parse(Handle::NULL, BlockPropertiesTable {
|
let view = parse(
|
||||||
parameter: Default::default(),
|
Handle::NULL,
|
||||||
fields: fields.clone(),
|
BlockPropertiesTable {
|
||||||
})?;
|
parameter: Default::default(),
|
||||||
|
fields: fields.clone(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
if view.columns.len() != columns.len()
|
if view.columns.len() != columns.len()
|
||||||
|| view.columns.iter().zip(columns).any(|(old, new)| {
|
|| view.columns.iter().zip(columns).any(|(old, new)| {
|
||||||
old.handle != new.handle
|
old.handle != new.handle
|
||||||
|| old.connection_name != new.connection_name
|
|| old.connection_name != new.connection_name
|
||||||
|| old.type_code != new.type_code
|
|| (old.type_code != new.type_code
|
||||||
|
&& old.node_id != 0
|
||||||
|
&& old.node_id != new.node_id)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return Err("Changing Block Properties Table columns is not supported for an existing table.");
|
return Err(
|
||||||
|
"Changing Block Properties Table columns is not supported for an existing table.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fields.truncate(view.row_start - 1);
|
fields.truncate(view.row_start - 1);
|
||||||
|
for column in columns {
|
||||||
|
if let Some(position) = fields.iter().enumerate().skip(4).find_map(|(i, f)|
|
||||||
|
(f.code == 340 && matches!(f.value, BlockPropertiesTableValue::Handle(h) if h == column.handle)).then_some(i)) {
|
||||||
|
let end = fields.iter().enumerate().skip(position + 1).find(|(_, f)| f.code == 340).map(|(i, _)| i).unwrap_or(fields.len());
|
||||||
|
if let Some(field) = fields.iter_mut().take(end).skip(position + 1).find(|f| f.code == 90) {
|
||||||
|
field.value = BlockPropertiesTableValue::Integer(i64::from(column.node_id));
|
||||||
|
}
|
||||||
|
if let Some(field) = fields.iter_mut().take(end).skip(position + 1).find(|f| f.code == 293) {
|
||||||
|
field.value = BlockPropertiesTableValue::Boolean(column.connection_name.eq_ignore_ascii_case("UpdatedAngle"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fields.push(int(92, rows.len() as i64));
|
fields.push(int(92, rows.len() as i64));
|
||||||
for (row_index, row) in rows.iter().enumerate() {
|
for (row_index, row) in rows.iter().enumerate() {
|
||||||
@@ -389,7 +538,7 @@ fn encode_fields(
|
|||||||
}
|
}
|
||||||
fields.push(int(93, rows.len() as i64));
|
fields.push(int(93, rows.len() as i64));
|
||||||
fields.extend([
|
fields.extend([
|
||||||
boolean(290, false),
|
boolean(290, must_match),
|
||||||
boolean(291, true),
|
boolean(291, true),
|
||||||
boolean(292, false),
|
boolean(292, false),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub enum Action {
|
|||||||
AddRow,
|
AddRow,
|
||||||
DeleteRow(usize),
|
DeleteRow(usize),
|
||||||
Apply,
|
Apply,
|
||||||
|
MustMatch(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
@@ -22,6 +23,7 @@ pub(crate) struct State {
|
|||||||
pub definition: Handle,
|
pub definition: Handle,
|
||||||
pub epoch: u64,
|
pub epoch: u64,
|
||||||
pub original: Option<TableView>,
|
pub original: Option<TableView>,
|
||||||
|
pub must_match: bool,
|
||||||
pub columns: Vec<Column>,
|
pub columns: Vec<Column>,
|
||||||
pub properties: Vec<Column>,
|
pub properties: Vec<Column>,
|
||||||
pub selected_property: Option<usize>,
|
pub selected_property: Option<usize>,
|
||||||
@@ -72,13 +74,16 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::Cell(row, col, value)
|
Action::Cell(row, col, value)
|
||||||
if row < self.rows.len() && col < self.columns.len() && col < self.rows[row].len() =>
|
if row < self.rows.len()
|
||||||
|
&& col < self.columns.len()
|
||||||
|
&& col < self.rows[row].len() =>
|
||||||
{
|
{
|
||||||
self.rows[row][col] = value;
|
self.rows[row][col] = value;
|
||||||
}
|
}
|
||||||
Action::AddRow if !self.columns.is_empty() && self.rows.len() < 1000 => {
|
Action::AddRow if !self.columns.is_empty() && self.rows.len() < 1000 => {
|
||||||
self.rows.push(vec!["0".into(); self.columns.len()]);
|
self.rows.push(vec!["0".into(); self.columns.len()]);
|
||||||
}
|
}
|
||||||
|
Action::MustMatch(value) => self.must_match = value,
|
||||||
Action::DeleteRow(row) if row < self.rows.len() => {
|
Action::DeleteRow(row) if row < self.rows.len() => {
|
||||||
self.rows.remove(row);
|
self.rows.remove(row);
|
||||||
}
|
}
|
||||||
@@ -93,7 +98,7 @@ pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Elem
|
|||||||
let rows = state.rows.len();
|
let rows = state.rows.len();
|
||||||
let mut headings = row![text(" ").width(28)].spacing(6);
|
let mut headings = row![text(" ").width(28)].spacing(6);
|
||||||
for (index, column_binding) in state.columns.iter().enumerate() {
|
for (index, column_binding) in state.columns.iter().enumerate() {
|
||||||
let mut header = column![text(column_binding.connection_name.clone()).size(13)];
|
let mut header = column![text(column_binding.display_name.clone()).size(13)];
|
||||||
if state.original.is_none() {
|
if state.original.is_none() {
|
||||||
header = header.push(
|
header = header.push(
|
||||||
button(text(crate::t!("Remove").into_owned()).size(11))
|
button(text(crate::t!("Remove").into_owned()).size(11))
|
||||||
@@ -136,7 +141,7 @@ pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Elem
|
|||||||
row![
|
row![
|
||||||
pick_list(state.selected_property, available, |i: &usize| state
|
pick_list(state.selected_property, available, |i: &usize| state
|
||||||
.properties[*i]
|
.properties[*i]
|
||||||
.connection_name
|
.display_name
|
||||||
.clone())
|
.clone())
|
||||||
.on_select(move |i| send(Action::Property(i)))
|
.on_select(move |i| send(Action::Property(i)))
|
||||||
.placeholder(crate::t!("Choose input property").into_owned())
|
.placeholder(crate::t!("Choose input property").into_owned())
|
||||||
@@ -158,12 +163,14 @@ pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Elem
|
|||||||
.height(Fill)
|
.height(Fill)
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
row![
|
row![
|
||||||
button(text(crate::t!("Add Row").into_owned())).on_press_maybe(
|
button(text(crate::t!("Add Row").into_owned()))
|
||||||
(cols > 0 && rows < 1000).then_some(send(Action::AddRow))
|
.on_press_maybe((cols > 0 && rows < 1000).then_some(send(Action::AddRow))),
|
||||||
),
|
|
||||||
Space::new().width(Fill)
|
Space::new().width(Fill)
|
||||||
]
|
]
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
|
iced::widget::checkbox(state.must_match)
|
||||||
|
.label(crate::t!("Block properties must match a row in the table").into_owned())
|
||||||
|
.on_toggle(move |value| send(Action::MustMatch(value))),
|
||||||
text(state.error.clone().unwrap_or_default())
|
text(state.error.clone().unwrap_or_default())
|
||||||
.size(12)
|
.size(12)
|
||||||
.style(text::danger),
|
.style(text::danger),
|
||||||
|
|||||||
Reference in new issue
Block a user