forked from 1b-agent/opencadstudio-upstream
Merge branch 'main' into feature/parametric-constraint-system
Brings in 54 commits from origin/main: a selection-performance overhaul, surface thickening, GPU-native wide/tapered stroke rendering, continued ocs_doc_api evolution, and assorted fixes/polish (block double-click editing, DWG hatch/database repair, clipboard, collapsible Properties sections). Six real conflicts, all where this branch had independently re-applied a fix upstream later revised or already carried natively: - src/app/automation.rs, src/app/command_driver.rs: combined both sides — kept this branch's sketch-constraint/named-parameter loading on "new"/"open", adopted main's Scene::clear()/material_base_dir and its newer rebuild_derived_caches() over the older bump_geometry(). Scene::clear() (src/scene/entity.rs) now also resets sketch_constraints/named_parameters, so every caller gets this for free instead of each call site repeating it. - src/io/mod.rs, src/scene/render_graph.rs: adopted main's design outright (commit973af87d, authored by Hakan directly) — a large MINSERT is no longer purged as "corrupt" at load, only its rendering is bounded, preserving the source data. This branch's own earlier design (reject at load) predates that revision. - src/scene/pipeline/mesh_gpu.rs: adopted main's valid_rgba_size() guard (a real robustness improvement over this branch's version). - src/snap.rs: replaced wholesale with main's version — this branch's copy was an explicit verbatim snapshot from an earlier upstream PR (commit4c264b8c) with zero constraint-system-specific content, and main has since evolved it considerably further. Also removed two now-dead duplicates the merge surfaced without a conflict marker (both branches had independently fixed the same things): the OPTIONS/OP command dispatch/autocomplete entries, and automation.rs's open_finalizes_and_purges_like_the_ui_open_path test (kept main's version, which covers strictly more: solid_models/ material_base_dir reset on "new"). Full workspace suite passes (812 lib tests + this branch's own 24 constraint/persistence integration tests) except the one pre-existing, unrelated font-test failure already present on main. Zero clippy findings in the hand-written merge resolution itself; the ~1,171 warnings clippy reports are pre-existing in the 54 absorbed commits, not introduced here. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
commit
d675244fb8
89 files changed
+5700
-1101
No files matched your search
@@ -849,10 +849,10 @@ impl CommandLine {
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.padding([2, 6]);
|
||||
.padding([2, 8]);
|
||||
let panel = container(column![header, log])
|
||||
.width(Length::Fill)
|
||||
.padding([4, 8]);
|
||||
.padding(Padding { top: 2.0, right: 8.0, bottom: 4.0, left: 8.0 });
|
||||
let resize = iced::widget::mouse_area(
|
||||
container(crate::ui::icons::themed_primary(
|
||||
crate::ui::icons::RESIZE,
|
||||
|
||||
+36
-4
@@ -447,6 +447,10 @@ pub struct PropertiesPanel {
|
||||
/// carried across panel rebuilds so the state survives edits and selection
|
||||
/// changes.
|
||||
pub expanded_groups: HashSet<String>,
|
||||
/// Property sections the user collapsed. This is a rendered copy of the
|
||||
/// app-wide preference, allowing every open document panel to share it.
|
||||
/// Empty by default, meaning every section is initially shown.
|
||||
pub collapsed_sections: HashSet<String>,
|
||||
/// Whether the editable-dropdown (block Name) option list is open.
|
||||
pub edit_choice_open: bool,
|
||||
/// Field key of the value row currently being edited, or `None`. Marked when
|
||||
@@ -485,6 +489,7 @@ impl Default for PropertiesPanel {
|
||||
prop_vertex: 0,
|
||||
prop_vertex_indicator_active: false,
|
||||
expanded_groups: HashSet::default(),
|
||||
collapsed_sections: HashSet::default(),
|
||||
edit_choice_open: false,
|
||||
active_field: None,
|
||||
field_key_by_id: HashMap::default(),
|
||||
@@ -710,8 +715,32 @@ impl PropertiesPanel {
|
||||
// ── Section renderer ──────────────────────────────────────────────────
|
||||
|
||||
fn render_section<'a>(&'a self, section: &'a PropSection) -> Element<'a, Message> {
|
||||
// Section header
|
||||
let hdr = container(text(§ion.title).size(10))
|
||||
let collapsed = self.collapsed_sections.contains(§ion.title);
|
||||
let toggle = button(if collapsed {
|
||||
crate::ui::icons::themed_arrow_right(10.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_arrow_down(10.0)
|
||||
})
|
||||
.on_press(Message::PropSectionToggle(section.title.clone()))
|
||||
.style(button::text)
|
||||
.padding([0, 3]);
|
||||
|
||||
// The arrow intentionally sits at the far end of the header. The rest
|
||||
// of the header responds to a double-click, leaving a single click on
|
||||
// the arrow as the precise, discoverable toggle target.
|
||||
let title = mouse_area(
|
||||
container(text(§ion.title).size(10))
|
||||
.width(Length::Fill)
|
||||
.padding([3, 8]),
|
||||
)
|
||||
.on_double_click(Message::PropSectionToggle(section.title.clone()));
|
||||
let hdr = container(
|
||||
row![
|
||||
title,
|
||||
toggle,
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
@@ -724,10 +753,12 @@ impl PropertiesPanel {
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.padding([3, 8]);
|
||||
.width(Length::Fill);
|
||||
|
||||
let mut col = column![hdr].spacing(0);
|
||||
if collapsed {
|
||||
return col.into();
|
||||
}
|
||||
|
||||
// Consecutive "<Base> X / <Base> Y [/ <Base> Z]" text rows collapse
|
||||
// into one clickable summary row; clicking expands the components.
|
||||
@@ -890,6 +921,7 @@ impl PropertiesPanel {
|
||||
| "text_color"
|
||||
| "block_content_color"
|
||||
| "background_fill_color"
|
||||
| "indicator_fill_color"
|
||||
) {
|
||||
let open = self.open_color_field.as_deref() == Some(field);
|
||||
let fsel = field.to_string();
|
||||
|
||||
+143
-5
@@ -13,6 +13,25 @@ pub enum OptionsTab {
|
||||
General,
|
||||
Display,
|
||||
Selection,
|
||||
Drawing,
|
||||
}
|
||||
|
||||
/// The Selection-card settings that live on `UserSettings` rather than on the
|
||||
/// model-space theme.
|
||||
///
|
||||
/// Gathered into one value instead of four more positional parameters: the
|
||||
/// function already takes seventeen, and `pick_add` / `pick_drag_rect` are two
|
||||
/// adjacent `bool`s that would swap silently at the call site.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SelectionPrefs {
|
||||
/// PICKBOX, 0..=50.
|
||||
pub pick_box: i32,
|
||||
/// PICKADD: true = a click adds to the selection.
|
||||
pub pick_add: bool,
|
||||
/// PICKDRAG: true = press-drag draws a rectangle instead of a lasso.
|
||||
pub pick_drag_rect: bool,
|
||||
/// GRIPOBJLIMIT, 0..=32767; 0 = no limit.
|
||||
pub grip_object_limit: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -37,7 +56,9 @@ pub fn view_window<'a>(
|
||||
language: crate::i18n::Language,
|
||||
active_tab: OptionsTab,
|
||||
cursor_size: i32,
|
||||
pick_box: i32,
|
||||
selection: SelectionPrefs,
|
||||
double_click_block_refedit: bool,
|
||||
double_click_block_attedit: bool,
|
||||
cursor_type: CursorType,
|
||||
crosshair_color: Option<[u8; 3]>,
|
||||
crosshair_color_input: &'a str,
|
||||
@@ -634,10 +655,12 @@ pub fn view_window<'a>(
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Pick box size")).size(12).width(140),
|
||||
slider(0..=50, pick_box.clamp(0, 50), Message::PickBoxChanged)
|
||||
slider(0..=50, selection.pick_box.clamp(0, 50), Message::PickBoxChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(pick_box.clamp(0, 50).to_string()).size(11).width(44),
|
||||
text(selection.pick_box.clamp(0, 50).to_string())
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
@@ -648,6 +671,30 @@ pub fn view_window<'a>(
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Selection Modes")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
// Checked is the inverse of PICKADD: plain clicks replace and Shift adds.
|
||||
iced::widget::checkbox(!selection.pick_add)
|
||||
.on_toggle(Message::ShiftToAddToggled)
|
||||
.size(15),
|
||||
text(crate::t!("Use Shift to add to selection (PICKADD)")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
iced::widget::checkbox(selection.pick_drag_rect)
|
||||
.on_toggle(Message::PickDragRectToggled)
|
||||
.size(15),
|
||||
text(crate::t!(
|
||||
"Press and drag draws a rectangle instead of a lasso (PICKDRAG)"
|
||||
))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(24),
|
||||
row![
|
||||
text(crate::t!("Visual Effect Settings")).size(15),
|
||||
Space::new().width(Fill),
|
||||
@@ -716,6 +763,15 @@ pub fn view_window<'a>(
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
iced::widget::checkbox(model_space.selection_effect)
|
||||
.on_toggle(Message::SelectionEffectToggled)
|
||||
.size(15),
|
||||
text(crate::t!("Show selection effect (SELECTIONEFFECT)")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selection highlight color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
@@ -729,6 +785,34 @@ pub fn view_window<'a>(
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Preview")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
// SELECTIONPREVIEW is a bitmask; bit 1 is the idle rollover and
|
||||
// bit 2 the one that runs while a command is gathering objects.
|
||||
iced::widget::checkbox(model_space.selection_preview & 1 != 0)
|
||||
.on_toggle(Message::SelectionPreviewIdleToggled)
|
||||
.size(15),
|
||||
text(crate::t!("Preview selection when no command is active")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
iced::widget::checkbox(model_space.selection_preview & 2 != 0)
|
||||
.on_toggle(Message::SelectionPreviewCommandToggled)
|
||||
.size(15),
|
||||
text(crate::t!("Preview selection during a command")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!(
|
||||
"Highlights the object under the cursor before it is picked (SELECTIONPREVIEW)."
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Grip Settings")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
@@ -747,6 +831,33 @@ pub fn view_window<'a>(
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Object limit for grips")).size(12).width(140),
|
||||
// Values above the slider range remain available through SETVAR.
|
||||
slider(
|
||||
0..=1000,
|
||||
selection.grip_object_limit.clamp(0, 1000),
|
||||
Message::GripObjectLimitChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(if selection.grip_object_limit == 0 {
|
||||
crate::t!("Unlimited").into_owned()
|
||||
} else {
|
||||
selection.grip_object_limit.to_string()
|
||||
})
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!(
|
||||
"Past this many selected objects no grips are drawn; 0 removes the limit (GRIPOBJLIMIT)."
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Unselected grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
@@ -789,10 +900,35 @@ pub fn view_window<'a>(
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
let drawing = column![
|
||||
text(crate::t!("Block Edit")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
iced::widget::checkbox(double_click_block_refedit)
|
||||
.on_toggle(Message::DoubleClickBlockRefeditChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Double-click to edit block in-place (REFEDIT)")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(18),
|
||||
row![
|
||||
iced::widget::checkbox(double_click_block_attedit)
|
||||
.on_toggle(Message::DoubleClickBlockAtteditChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Double-click attributed blocks to edit attributes (ATTEDIT)")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
let content: Element<'a, Message> = match active_tab {
|
||||
OptionsTab::General => general.into(),
|
||||
OptionsTab::Display => display_element.into(),
|
||||
OptionsTab::Selection => selection.into(),
|
||||
OptionsTab::Drawing => drawing.into(),
|
||||
};
|
||||
|
||||
let tab_button = |label, tab| {
|
||||
@@ -806,6 +942,7 @@ pub fn view_window<'a>(
|
||||
tab_button(crate::t!("General"), OptionsTab::General),
|
||||
tab_button(crate::t!("Display"), OptionsTab::Display),
|
||||
tab_button(crate::t!("Selection"), OptionsTab::Selection),
|
||||
tab_button(crate::t!("Drawing"), OptionsTab::Drawing),
|
||||
]
|
||||
.spacing(6);
|
||||
|
||||
@@ -821,10 +958,11 @@ pub fn view_window<'a>(
|
||||
.width(sizing.width)
|
||||
.height(sizing.height);
|
||||
|
||||
let intrinsic = sizing.width == crate::ui::modal::ModalSizing::INTRINSIC.width;
|
||||
container(body)
|
||||
.style(container::rounded_box)
|
||||
.padding([16, 18])
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.width(if intrinsic { iced::Length::Fixed(540.0) } else { sizing.width })
|
||||
.height(if intrinsic { iced::Length::Fixed(560.0) } else { sizing.height })
|
||||
.into()
|
||||
}
|
||||
Reference in new issue
Block a user