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 (commit 973af87d, 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
  (commit 4c264b8c) 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 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-09 20:44:38 +02:00
commit d675244fb8
89 files changed
+5700 -1101

No files matched your search

+121 -3
View File
@@ -1839,10 +1839,32 @@ impl OpenCADStudio {
Message::CommandHistoryCopy => {
let text = self.command_line.history_plain_text();
if text.is_empty() {
Task::none()
} else {
iced::clipboard::write(text).discard()
return Task::none();
}
#[cfg(target_arch = "wasm32")]
{
let promise = crate::sys::copy_history_text(
&text,
crate::t!("Clipboard access is unavailable. Press Ctrl+C or Command+C to copy the selected history.").as_ref(),
crate::t!("Close").as_ref(),
);
Task::perform(async move {
wasm_bindgen_futures::JsFuture::from(promise).await
.ok().and_then(|value| value.as_bool()).unwrap_or(false)
}, Message::CommandHistoryCopied)
}
#[cfg(not(target_arch = "wasm32"))]
iced::clipboard::write(text).discard()
}
#[cfg(target_arch = "wasm32")]
Message::CommandHistoryCopied(copied) => {
if copied {
self.command_line.push_info(crate::t!("Copied").as_ref());
} else {
self.command_line.push_error(crate::t!("Clipboard access is unavailable. Press Ctrl+C or Command+C to copy the selected history.").as_ref());
}
Task::none()
}
Message::CommandHistoryClear => {
@@ -5108,6 +5130,20 @@ impl OpenCADStudio {
Task::none()
}
Message::PropSectionToggle(title) => {
let sections = &mut self.collapsed_property_sections;
if !sections.remove(&title) {
sections.insert(title);
}
// Keep already-built panels in other open documents in sync,
// rather than waiting for each one to rebuild on selection.
let sections = self.collapsed_property_sections.clone();
for tab in &mut self.tabs {
tab.properties.collapsed_sections = sections.clone();
}
Task::none()
}
Message::PropEditChoiceToggle => {
let panel = &mut self.tabs[self.active_tab].properties;
panel.edit_choice_open = !panel.edit_choice_open;
@@ -5216,6 +5252,21 @@ impl OpenCADStudio {
Message::PropColorFieldChanged { field, color } => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if field == "indicator_fill_color" {
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(acadrust::EntityType::Extended(extended)) =
app.tabs[i].scene.document.get_entity_mut(handle)
{
if let acadrust::entities::ExtendedEntityData::SectionObject(data) =
&mut extended.data
{
data.indicator_color = color;
}
}
});
self.tabs[i].properties.open_color_field = None;
return Task::none();
}
// Dim-line colour override (Leader / Dimension): write it as an
// ACAD_DSTYLE code-176 override (an ACI index) so it round-trips
// through DWG and DXF. RGB picks collapse to the nearest ACI, in
@@ -5921,6 +5972,18 @@ impl OpenCADStudio {
Task::none()
}
Message::DoubleClickBlockRefeditChanged(enabled) => {
self.double_click_block_refedit = enabled;
self.persist_settings_if_changed();
Task::none()
}
Message::DoubleClickBlockAtteditChanged(enabled) => {
self.double_click_block_attedit = enabled;
self.persist_settings_if_changed();
Task::none()
}
Message::CursorTypeChanged(value) => {
self.cursor_type = value;
self.persist_settings_if_changed();
@@ -6148,6 +6211,48 @@ impl OpenCADStudio {
Task::none()
}
Message::GripObjectLimitChanged(limit) => {
self.grip_object_limit = limit.clamp(0, 32767);
self.persist_settings_if_changed();
Task::none()
}
Message::SelectionEffectToggled(enabled) => {
self.model_space.selection_effect = enabled;
self.sync_model_space_theme(false);
self.persist_settings_if_changed();
Task::none()
}
// SELECTIONPREVIEW is a bitmask and the two checkboxes own one bit
// each: 1 = rollover while idle, 2 = rollover during a command.
Message::SelectionPreviewIdleToggled(enabled) => {
self.model_space.selection_preview =
set_preview_bit(self.model_space.selection_preview, 1, enabled);
self.persist_settings_if_changed();
Task::none()
}
Message::SelectionPreviewCommandToggled(enabled) => {
self.model_space.selection_preview =
set_preview_bit(self.model_space.selection_preview, 2, enabled);
self.persist_settings_if_changed();
Task::none()
}
// "Use Shift to add" is the inverse of PICKADD.
Message::ShiftToAddToggled(shift_to_add) => {
self.pick_add = !shift_to_add;
self.persist_settings_if_changed();
Task::none()
}
Message::PickDragRectToggled(rectangle) => {
self.pick_drag_rect = rectangle;
self.persist_settings_if_changed();
Task::none()
}
Message::RestoreSelectionVisualDefaults => {
self.model_space.selection_area = true;
self.model_space.selection_opacity = 12;
@@ -6160,6 +6265,10 @@ impl OpenCADStudio {
self.model_space.grip_color = 0;
self.model_space.grip_hot = 0;
self.model_space.grip_hover = 0;
// The button restores the *visual* defaults, which is why the
// grip limit is here and PICKADD / PICKDRAG are not — those are
// how selection behaves, not how it looks.
self.grip_object_limit = crate::app::settings::DEFAULT_GRIP_OBJECT_LIMIT;
self.sync_model_space_theme(false);
self.persist_settings_if_changed();
Task::none()
@@ -8677,3 +8786,12 @@ mod free_text_entry_tests {
assert_eq!(app.text_entry_mode(), TextEntryMode::Command);
}
}
/// Set or clear one bit of `SELECTIONPREVIEW`.
fn set_preview_bit(current: u8, bit: u8, enabled: bool) -> u8 {
if enabled {
current | bit
} else {
current & !bit
}
}