forked from 1b-agent/opencadstudio-upstream
Merge upstream main; codec and kernel patches on branches merged with it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
0158ea533f
48 files changed
+5133
-52
No files matched your search
@@ -1023,6 +1023,8 @@ pub(crate) fn selection_overlay<'a>(
|
||||
pub(crate) struct AuthoringDimension {
|
||||
pub lines: Vec<[Point; 2]>,
|
||||
pub round_grips: Vec<Point>,
|
||||
pub base_grips: Vec<Point>,
|
||||
pub lookup_grips: Vec<Point>,
|
||||
pub dimensions: Vec<[Point; 2]>,
|
||||
pub labels: Vec<(Point, String)>,
|
||||
pub grips: Vec<(Point, Option<Point>)>,
|
||||
@@ -2414,6 +2416,35 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
for point in &dimension.base_grips {
|
||||
let stroke = canvas::Stroke::default().with_color(color);
|
||||
frame.stroke(&canvas::Path::circle(*point, 6.0), stroke);
|
||||
frame.stroke(
|
||||
&canvas::Path::line(
|
||||
Point::new(point.x - 9.0, point.y),
|
||||
Point::new(point.x + 9.0, point.y),
|
||||
),
|
||||
stroke,
|
||||
);
|
||||
frame.stroke(
|
||||
&canvas::Path::line(
|
||||
Point::new(point.x, point.y - 9.0),
|
||||
Point::new(point.x, point.y + 9.0),
|
||||
),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
for point in &dimension.lookup_grips {
|
||||
frame.fill(
|
||||
&canvas::Path::new(|p| {
|
||||
p.move_to(Point::new(point.x - 5.0, point.y - 4.0));
|
||||
p.line_to(Point::new(point.x + 5.0, point.y - 4.0));
|
||||
p.line_to(Point::new(point.x, point.y + 5.0));
|
||||
p.close();
|
||||
}),
|
||||
color,
|
||||
);
|
||||
}
|
||||
for point in &dimension.round_grips {
|
||||
frame.fill(&canvas::Path::circle(*point, 3.5), color);
|
||||
}
|
||||
|
||||
@@ -193,6 +193,9 @@ fn catalog(
|
||||
visibility_error: Option<&'static str>,
|
||||
point_error: Option<&'static str>,
|
||||
flip_error: Option<&'static str>,
|
||||
lookup_error: Option<&'static str>,
|
||||
move_error: Option<&'static str>,
|
||||
scale_error: Option<&'static str>,
|
||||
) -> Element<'static, Message> {
|
||||
let (entries, unavailable) = match tab {
|
||||
Tab::Parameters => (
|
||||
@@ -212,7 +215,11 @@ fn catalog(
|
||||
let distance = tab == Tab::Parameters && matches!(*label, "Linear" | "Polar" | "XY");
|
||||
let orientation = tab == Tab::Parameters && matches!(*label,"Rotation"|"Alignment"|"Flip");
|
||||
let orientation_error = if *label=="Flip" {flip_error} else {point_error};
|
||||
let enabled = (orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
|
||||
let single=tab==Tab::Parameters&&matches!(*label,"Lookup"|"Basepoint");
|
||||
let lookup_action=tab==Tab::Actions&&*label=="Lookup";
|
||||
let geometry_action=tab==Tab::Actions&&matches!(*label,"Move"|"Scale");
|
||||
let action_error=if *label=="Scale" {scale_error}else{move_error};
|
||||
let enabled = (geometry_action&&action_error.is_none())||(single&&point_error.is_none())||(lookup_action&&lookup_error.is_none())||(orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
|
||||
let base = if enabled {
|
||||
icons::semantic(icon, 28.0)
|
||||
} else {
|
||||
@@ -221,10 +228,11 @@ fn catalog(
|
||||
let icon = if tab != Tab::Parameters && *label != "Block Properties Table" {
|
||||
stack![
|
||||
base,
|
||||
container(icons::semantic_disabled(
|
||||
include_bytes!("../../../assets/icons/ui/bolt.svg"),
|
||||
14.0
|
||||
))
|
||||
container(if enabled {
|
||||
icons::semantic(include_bytes!("../../../assets/icons/ui/bolt.svg"), 14.0)
|
||||
} else {
|
||||
icons::semantic_disabled(include_bytes!("../../../assets/icons/ui/bolt.svg"), 14.0)
|
||||
})
|
||||
.width(28)
|
||||
.height(28)
|
||||
.align_right(Fill)
|
||||
@@ -235,8 +243,8 @@ fn catalog(
|
||||
base
|
||||
};
|
||||
let message = enabled.then(|| Message::RibbonToolClick {
|
||||
tool_id: format!("BPARAMETER_{}", label.to_ascii_uppercase()),
|
||||
event: crate::modules::ModuleEvent::Command(format!("BPARAMETER {}", label.to_ascii_uppercase())),
|
||||
tool_id: if geometry_action {format!("BACTION_{}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else{format!("BPARAMETER_{}", label.to_ascii_uppercase())},
|
||||
event: crate::modules::ModuleEvent::Command(if geometry_action {format!("BACTION {}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else{format!("BPARAMETER {}", label.to_ascii_uppercase())}),
|
||||
});
|
||||
let description = if visibility {
|
||||
crate::t!(visibility_error
|
||||
@@ -253,6 +261,12 @@ fn catalog(
|
||||
"Alignment" => "Place the alignment base point and tangent direction.",
|
||||
_ => "Place a reflection line and its flip-state label. Associate a Flip action to mirror geometry.",
|
||||
})).into_owned()
|
||||
} else if single {
|
||||
crate::t!(point_error.unwrap_or(if *label=="Basepoint" {"Place or relocate the dynamic block insertion anchor."}else{"Place a Lookup parameter, then use the Lookup action to edit its property table."})).into_owned()
|
||||
} else if geometry_action {
|
||||
crate::t!(action_error.unwrap_or("Select a parameter endpoint, select the objects it controls, then place the action marker. Test it using the parameter grip in Test Block.")).into_owned()
|
||||
} else if lookup_action {
|
||||
crate::t!(lookup_error.unwrap_or("Create or edit property Lookup tables for this block.")).into_owned()
|
||||
} else {
|
||||
unavailable.to_string()
|
||||
};
|
||||
@@ -265,6 +279,12 @@ fn catalog(
|
||||
container(entry).id(match *label { "Linear" => "authoring-tool-linear", "Polar" => "authoring-tool-polar", _ => "authoring-tool-xy" }).into()
|
||||
} else if orientation {
|
||||
container(entry).id(match *label {"Rotation"=>"authoring-tool-rotation","Alignment"=>"authoring-tool-alignment",_=>"authoring-tool-flip"}).into()
|
||||
} else if single {
|
||||
container(entry).id(if *label=="Lookup" {"authoring-tool-lookup"}else{"authoring-tool-basepoint"}).into()
|
||||
} else if geometry_action {
|
||||
container(entry).id(if *label=="Move" {"authoring-tool-move-action"}else{"authoring-tool-scale-action"}).into()
|
||||
} else if lookup_action {
|
||||
container(entry).id("authoring-tool-lookup-table").into()
|
||||
} else {
|
||||
entry
|
||||
}
|
||||
@@ -378,6 +398,9 @@ pub fn view(
|
||||
visibility_error: Option<&'static str>,
|
||||
point_error: Option<&'static str>,
|
||||
flip_error: Option<&'static str>,
|
||||
lookup_error: Option<&'static str>,
|
||||
move_error: Option<&'static str>,
|
||||
scale_error: Option<&'static str>,
|
||||
) -> Element<'static, Message> {
|
||||
let tabs = column(Tab::ALL.into_iter().map(|category| {
|
||||
let label = category.label();
|
||||
@@ -431,7 +454,10 @@ pub fn view(
|
||||
selected_tab,
|
||||
visibility_error,
|
||||
point_error,
|
||||
flip_error
|
||||
flip_error,
|
||||
lookup_error,
|
||||
move_error,
|
||||
scale_error
|
||||
))
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
use crate::app::Message;
|
||||
use crate::scene::block_lookup::{self, Property};
|
||||
use codec::{
|
||||
objects::{BlockLookupAction, BlockLookupParameter},
|
||||
Handle,
|
||||
};
|
||||
use iced::{
|
||||
widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
},
|
||||
Element, Fill,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
Select(usize),
|
||||
Property(usize),
|
||||
AddProperty,
|
||||
RemoveProperty(usize),
|
||||
Cell(usize, usize, String),
|
||||
AddRow,
|
||||
DeleteRow(usize),
|
||||
MoveRow(usize, bool),
|
||||
Reverse(usize, bool),
|
||||
Audit,
|
||||
Apply,
|
||||
}
|
||||
|
||||
pub(crate) struct Draft {
|
||||
pub parameter: Handle,
|
||||
pub original_parameter: BlockLookupParameter,
|
||||
pub original: Option<(Handle, BlockLookupAction)>,
|
||||
pub edited: BlockLookupAction,
|
||||
}
|
||||
impl Draft {
|
||||
pub(crate) fn changed(&self) -> bool {
|
||||
match &self.original {
|
||||
Some((_, original)) => original != &self.edited,
|
||||
None => self.edited != block_lookup::empty_table(&self.original_parameter),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) struct State {
|
||||
pub document: u64,
|
||||
pub definition: Handle,
|
||||
pub epoch: u64,
|
||||
pub drafts: Vec<Draft>,
|
||||
pub selected: usize,
|
||||
pub properties: Vec<Property>,
|
||||
pub selected_property: Option<usize>,
|
||||
pub error: Option<String>,
|
||||
pub notice: Option<String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) fn edit(&mut self, action: Action) {
|
||||
self.error = None;
|
||||
self.notice = None;
|
||||
match action {
|
||||
Action::Select(index) if index < self.drafts.len() => {
|
||||
self.selected = index;
|
||||
self.selected_property = None;
|
||||
return;
|
||||
}
|
||||
Action::Property(index) if index < self.properties.len() => {
|
||||
self.selected_property = Some(index);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let Some(draft) = self.drafts.get_mut(self.selected) else {
|
||||
return;
|
||||
};
|
||||
let table = &mut draft.edited;
|
||||
let cols = table.columns.len();
|
||||
let rows = table.row_count.max(0) as usize;
|
||||
match action {
|
||||
Action::AddProperty => {
|
||||
let Some(property) = self.selected_property.and_then(|i| self.properties.get(i))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if cols >= 64
|
||||
|| table.columns.iter().any(|c| {
|
||||
c.node_id == property.column.node_id
|
||||
&& c.connection_name == property.column.connection_name
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
let index = table
|
||||
.columns
|
||||
.iter()
|
||||
.position(|c| c.lookup_property)
|
||||
.unwrap_or(cols);
|
||||
for row in (0..rows).rev() {
|
||||
table
|
||||
.expressions
|
||||
.insert(row * cols + index, property.value.clone());
|
||||
}
|
||||
table.columns.insert(index, property.column.clone());
|
||||
table.column_count += 1;
|
||||
self.selected_property = None;
|
||||
}
|
||||
Action::RemoveProperty(index)
|
||||
if table.columns.get(index).is_some_and(|c| !c.lookup_property) =>
|
||||
{
|
||||
for row in (0..rows).rev() {
|
||||
table.expressions.remove(row * cols + index);
|
||||
}
|
||||
table.columns.remove(index);
|
||||
table.column_count -= 1;
|
||||
}
|
||||
Action::Cell(row, col, value) if row < rows && col < cols => {
|
||||
table.expressions[row * cols + col] = value;
|
||||
}
|
||||
Action::AddRow if rows < 1000 => {
|
||||
for column in &table.columns {
|
||||
let value = if column.lookup_property {
|
||||
format!("Value{}", rows + 1)
|
||||
} else {
|
||||
self.properties
|
||||
.iter()
|
||||
.find(|p| {
|
||||
p.column.node_id == column.node_id
|
||||
&& p.column.connection_name == column.connection_name
|
||||
})
|
||||
.map(|p| p.value.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
table.expressions.push(value);
|
||||
}
|
||||
table.row_count += 1;
|
||||
}
|
||||
Action::DeleteRow(row) if row < rows => {
|
||||
table.expressions.drain(row * cols..(row + 1) * cols);
|
||||
table.row_count -= 1;
|
||||
}
|
||||
Action::MoveRow(row, up) if row < rows => {
|
||||
let next = if up {
|
||||
row.checked_sub(1)
|
||||
} else {
|
||||
(row + 1 < rows).then_some(row + 1)
|
||||
};
|
||||
if let Some(next) = next {
|
||||
for col in 0..cols {
|
||||
table.expressions.swap(row * cols + col, next * cols + col);
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::Reverse(index, value)
|
||||
if table.columns.get(index).is_some_and(|c| c.lookup_property) =>
|
||||
{
|
||||
table.columns[index].writable = value;
|
||||
}
|
||||
Action::Audit => {
|
||||
match block_lookup::validate(table, draft.original.as_ref().map(|(_, t)| t)) {
|
||||
Ok(()) => self.notice = Some(crate::t!("Table audit passed.").into_owned()),
|
||||
Err(error) => self.error = Some(crate::t!(error.as_str()).into_owned()),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Element<'_, Message> {
|
||||
let send = |a| Message::BlockLookup(a);
|
||||
let draft = &state.drafts[state.selected];
|
||||
let table = &draft.edited;
|
||||
let cols = table.columns.len();
|
||||
let rows = table.row_count.max(0) as usize;
|
||||
let mut headings = row![text(" ").width(28)].spacing(6);
|
||||
for (index, column) in table.columns.iter().enumerate() {
|
||||
let name = if column.lookup_property {
|
||||
state
|
||||
.drafts
|
||||
.iter()
|
||||
.find(|d| {
|
||||
d.original_parameter
|
||||
.parameter
|
||||
.parameter
|
||||
.element
|
||||
.eval
|
||||
.node_id
|
||||
== column.node_id
|
||||
})
|
||||
.map(|d| d.original_parameter.lookup_name.clone())
|
||||
.unwrap_or_else(|| crate::t!("Lookup value").into_owned())
|
||||
} else {
|
||||
state
|
||||
.properties
|
||||
.iter()
|
||||
.find(|p| {
|
||||
p.column.node_id == column.node_id
|
||||
&& p.column.connection_name == column.connection_name
|
||||
})
|
||||
.map(|p| p.label.clone())
|
||||
.unwrap_or_else(|| crate::t!("Input property").into_owned())
|
||||
};
|
||||
let mut header = column![text(name).size(13)];
|
||||
if column.lookup_property {
|
||||
header = header.push(
|
||||
row![
|
||||
checkbox(column.writable).on_toggle(move |v| send(Action::Reverse(index, v))),
|
||||
text(crate::t!("Reverse lookup").into_owned()).size(11)
|
||||
]
|
||||
.spacing(4),
|
||||
);
|
||||
} else {
|
||||
header = header.push(
|
||||
button(text(crate::t!("Remove").into_owned()).size(11))
|
||||
.on_press(send(Action::RemoveProperty(index))),
|
||||
);
|
||||
}
|
||||
headings = headings.push(container(header.spacing(4)).width(160));
|
||||
}
|
||||
let mut grid = column![headings].spacing(7);
|
||||
for row_index in 0..rows {
|
||||
let mut cells = row![text((row_index + 1).to_string()).width(28)].spacing(6);
|
||||
for col_index in 0..cols {
|
||||
cells = cells.push(
|
||||
text_input("", &table.expressions[row_index * cols + col_index])
|
||||
.on_input(move |v| send(Action::Cell(row_index, col_index, v)))
|
||||
.width(160)
|
||||
.style(crate::ui::style::form::field_style),
|
||||
);
|
||||
}
|
||||
cells =
|
||||
cells
|
||||
.push(button(text("↑")).on_press_maybe(
|
||||
(row_index > 0).then_some(send(Action::MoveRow(row_index, true))),
|
||||
))
|
||||
.push(button(text("↓")).on_press_maybe(
|
||||
(row_index + 1 < rows).then_some(send(Action::MoveRow(row_index, false))),
|
||||
))
|
||||
.push(
|
||||
button(text(crate::t!("Delete").into_owned()))
|
||||
.on_press(send(Action::DeleteRow(row_index))),
|
||||
);
|
||||
grid = grid.push(cells);
|
||||
}
|
||||
let available = state
|
||||
.properties
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, p)| {
|
||||
!table.columns.iter().any(|c| {
|
||||
c.node_id == p.column.node_id && c.connection_name == p.column.connection_name
|
||||
})
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect::<Vec<_>>();
|
||||
container(
|
||||
column![
|
||||
row![
|
||||
text(crate::t!("Lookup parameter").into_owned()),
|
||||
pick_list(
|
||||
Some(state.selected),
|
||||
(0..state.drafts.len()).collect::<Vec<_>>(),
|
||||
|i: &usize| state.drafts[*i].original_parameter.lookup_name.clone()
|
||||
)
|
||||
.on_select(move |i| send(Action::Select(i)))
|
||||
.width(Fill)
|
||||
]
|
||||
.spacing(10),
|
||||
row![
|
||||
pick_list(state.selected_property, available, |i: &usize| state
|
||||
.properties[*i]
|
||||
.label
|
||||
.clone())
|
||||
.on_select(move |i| send(Action::Property(i)))
|
||||
.placeholder(crate::t!("Choose input property").into_owned())
|
||||
.width(Fill),
|
||||
button(text(crate::t!("Add Property").into_owned()))
|
||||
.on_press_maybe(state.selected_property.map(|_| send(Action::AddProperty)))
|
||||
]
|
||||
.spacing(8),
|
||||
text(
|
||||
crate::t!(
|
||||
"Add Linear or Polar distance inputs. Existing table columns are retained."
|
||||
)
|
||||
.into_owned()
|
||||
)
|
||||
.size(12),
|
||||
scrollable(grid)
|
||||
.direction(scrollable::Direction::Both {
|
||||
vertical: Default::default(),
|
||||
horizontal: Default::default()
|
||||
})
|
||||
.height(Fill)
|
||||
.width(Fill),
|
||||
row![
|
||||
button(text(crate::t!("Add Row").into_owned()))
|
||||
.on_press_maybe((rows < 1000).then_some(send(Action::AddRow))),
|
||||
button(text(crate::t!("Audit").into_owned())).on_press(send(Action::Audit)),
|
||||
Space::new().width(Fill)
|
||||
]
|
||||
.spacing(8),
|
||||
text(state.error.clone().unwrap_or_default())
|
||||
.size(12)
|
||||
.style(text::danger),
|
||||
text(state.notice.clone().unwrap_or_default()).size(12),
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("OK").into_owned()))
|
||||
.on_press(send(Action::Apply))
|
||||
.style(button::primary),
|
||||
button(text(crate::t!("Cancel").into_owned())).on_press(Message::CloseModal)
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.padding(16)
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
}
|
||||
@@ -11,6 +11,7 @@ pub mod block_edit_picker;
|
||||
pub mod block_palette;
|
||||
pub mod block_save_as;
|
||||
pub mod block_visibility;
|
||||
pub mod block_lookup;
|
||||
pub mod browser;
|
||||
pub mod count_palette;
|
||||
pub mod drafting_settings;
|
||||
|
||||
Reference in new issue
Block a user