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

+14 -1
View File
@@ -734,7 +734,7 @@ impl OpenCADStudio {
}
// ── EXTRUDE ────────────────────────────────────────────────────
"EXTRUDE" | "THICKEN" => {
"EXTRUDE" => {
use crate::modules::insert::solid3d_cmds::ExtrudeCommand;
// A preselection becomes the complete source set; otherwise
// the interactive command gathers any number of profiles.
@@ -769,6 +769,19 @@ impl OpenCADStudio {
}
}
"THICKEN" => {
use crate::modules::insert::solid3d_cmds::ThickenCommand;
let selected = self.tabs[i]
.scene
.selected_entities()
.into_iter()
.map(|(handle, entity)| (handle, entity.clone()))
.collect();
let command = ThickenCommand::new(selected);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
"PRESSPULL" => {
use crate::modules::insert::solid3d_cmds::PresspullCommand;
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
+58
View File
@@ -1400,6 +1400,64 @@ impl OpenCADStudio {
}
}
"SECTIONPLANE" => {
use crate::modules::model::sectionplane_cmd::SectionPlaneCommand;
let (bounds, next_name) = {
let scene = &mut self.tabs[i].scene;
let solid_handles = scene
.document
.entities()
.filter_map(|entity| {
matches!(
entity,
acadrust::EntityType::Solid3D(_)
| acadrust::EntityType::Surface(_)
| acadrust::EntityType::Region(_)
| acadrust::EntityType::Body(_)
)
.then_some(entity.common().handle)
})
.collect::<Vec<_>>();
scene.restore_solid_models(&solid_handles);
let bounds = solid_handles
.iter()
.filter_map(|handle| scene.solid_models.get(handle))
.filter_map(crate::scene::model::solid_model::extent)
.fold(None::<(glam::DVec3, glam::DVec3)>, |bounds, (low, high)| {
let low = glam::DVec3::from_array(low);
let high = glam::DVec3::from_array(high);
Some(match bounds {
None => (low, high),
Some((min, max)) => (min.min(low), max.max(high)),
})
});
let next_name = scene
.document
.entities()
.filter_map(|entity| {
let acadrust::EntityType::Extended(extended) = entity else {
return None;
};
let acadrust::entities::ExtendedEntityData::SectionObject(data) =
&extended.data
else {
return None;
};
data.name
.strip_prefix("Section Plane (")
.and_then(|name| name.strip_suffix(')'))
.and_then(|name| name.parse::<usize>().ok())
})
.max()
.unwrap_or(0)
+ 1;
(bounds, next_name)
};
let command = SectionPlaneCommand::new(bounds, next_name);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
// 3DALIGN <18 numbers> — align the selected solid by 3 source→3 dest points.
cmd if cmd == "3DALIGN"
|| cmd == "ALIGN3D"
+2
View File
@@ -465,6 +465,7 @@ inventory::submit!(crate::command::CommandRegistration {
"3DALIGN",
"ALIGN3D",
"SECTION",
"SECTIONPLANE",
"PYRAMID",
"PYR",
"SPLINEFIT",
@@ -551,6 +552,7 @@ inventory::submit!(crate::command::CommandRegistration {
"GRIPCOLOR",
"GRIPHOT",
"GRIPHOVER",
"GRIPOBJLIMIT",
// Reset selected entities' overrides to follow their layer.
"SETBYLAYER",
// Remove duplicate objects; set drawing base point; audit integrity;
+26 -1
View File
@@ -918,6 +918,7 @@ impl OpenCADStudio {
| "GRIPCOLOR"
| "GRIPHOT"
| "GRIPHOVER"
| "GRIPOBJLIMIT"
) =>
{
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
@@ -940,7 +941,7 @@ impl OpenCADStudio {
let value = it.next().map(|s| s.trim().to_string());
if name.is_empty() || name == "?" {
self.command_line.push_info(
crate::t!("SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER | CLAYER CELTYPE TEXTSTYLE (read-only)").as_ref(),
crate::t!("SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER GRIPOBJLIMIT | CLAYER CELTYPE TEXTSTYLE (read-only)").as_ref(),
);
} else {
if matches!(name.as_str(), "SHOWHIST" | "SOLIDHIST") {
@@ -1444,6 +1445,22 @@ impl OpenCADStudio {
},
None => Ok((format!("GRIPSIZE = {}", self.model_space.grip_size), false)),
},
"GRIPOBJLIMIT" => match &value {
Some(v) => match v.parse::<i32>() {
Ok(limit @ 0..=32767) => {
self.grip_object_limit = limit;
Ok((format!("GRIPOBJLIMIT = {limit}"), true))
}
_ => Err(
"SETVAR: integer from 0 to 32767 required (0 = no limit)."
.into(),
),
},
None => Ok((
format!("GRIPOBJLIMIT = {}", self.grip_object_limit),
false,
)),
},
"GRIPCOLOR" => match &value {
Some(v) => match v.parse::<u8>() {
Ok(color) => {
@@ -2126,6 +2143,7 @@ impl OpenCADStudio {
| "GRIPCOLOR"
| "GRIPHOT"
| "GRIPHOVER"
| "GRIPOBJLIMIT"
) {
self.persist_settings_if_changed();
} else {
@@ -2982,6 +3000,13 @@ mod tests {
// SELECTIONPREVIEW
let _ = app.run_command_line("SETVAR SELECTIONPREVIEW 2");
assert_eq!(app.model_space.selection_preview, 2);
let _ = app.run_command_line("SETVAR GRIPOBJLIMIT 0");
assert_eq!(app.grip_object_limit, 0);
let _ = app.run_command_line("GRIPOBJLIMIT 32767");
assert_eq!(app.grip_object_limit, 32767);
let _ = app.run_command_line("GRIPOBJLIMIT 32768");
assert_eq!(app.grip_object_limit, 32767);
}
#[test]
+10
View File
@@ -1566,6 +1566,16 @@ mod tests {
app
}
#[test]
fn options_and_its_alias_dispatch_from_the_start_page() {
let mut app = fresh_app();
let i = app.active_tab;
assert!(app.dispatch_view("OPTIONS", i).is_some());
assert!(app.dispatch_view("OP", i).is_some());
assert!(crate::app::commands::start_allowed("OPTIONS"));
assert!(crate::app::commands::start_allowed("OP"));
}
#[test]
fn hatchtoback_no_hatches_warns_and_noop() {
let mut app = fresh_app();