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 <[email protected]>
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

+115 -1
View File
@@ -143,6 +143,106 @@ impl Scene {
let _ = self.document.app_ids.add(app_id);
}
fn prepare_section_objects(&mut self, entity: &mut EntityType) -> bool {
use acadrust::entities::ExtendedEntityData;
use acadrust::objects::{
ClassObject, ClassObjectData, SectionManager, SectionSettings,
SectionTypeSettings,
};
let EntityType::Extended(extended) = entity else {
return false;
};
let ExtendedEntityData::SectionObject(data) = &mut extended.data else {
return false;
};
if extended.common.handle.is_null() {
extended.common.handle = self.document.allocate_handle();
}
let entity_handle = extended.common.handle;
let valid_settings = matches!(
self.document.objects.get(&data.settings_handle),
Some(ObjectType::ClassObject(object))
if matches!(&object.data, ClassObjectData::SectionSettings(_))
);
if !valid_settings {
let settings_handle = self.document.allocate_handle();
let mut settings = ClassObject::new(ClassObjectData::SectionSettings(
SectionSettings {
current_type: 4,
types: vec![SectionTypeSettings {
section_type: 4,
generation: 17,
..SectionTypeSettings::default()
}],
},
));
settings.handle = settings_handle;
settings.owner = entity_handle;
self.document
.objects
.insert(settings_handle, ObjectType::ClassObject(settings));
data.settings_handle = settings_handle;
} else if let Some(ObjectType::ClassObject(settings)) =
self.document.objects.get_mut(&data.settings_handle)
{
settings.owner = entity_handle;
}
let root = crate::scene::annotative::root_named_dict_handle(&mut self.document);
let manager_handle = self
.document
.objects
.get(&root)
.and_then(|object| match object {
ObjectType::Dictionary(dictionary) => dictionary
.entries
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case("ACAD_SECTION_MANAGER"))
.map(|(_, handle)| *handle),
_ => None,
})
.filter(|handle| {
matches!(
self.document.objects.get(handle),
Some(ObjectType::ClassObject(object))
if matches!(&object.data, ClassObjectData::SectionManager(_))
)
})
.unwrap_or_else(|| {
let handle = self.document.allocate_handle();
let mut manager =
ClassObject::new(ClassObjectData::SectionManager(SectionManager::default()));
manager.handle = handle;
manager.owner = root;
self.document
.objects
.insert(handle, ObjectType::ClassObject(manager));
if let Some(ObjectType::Dictionary(dictionary)) =
self.document.objects.get_mut(&root)
{
dictionary.entries.retain(|(name, _)| {
!name.eq_ignore_ascii_case("ACAD_SECTION_MANAGER")
});
dictionary.add_entry("ACAD_SECTION_MANAGER", handle);
}
handle
});
if let Some(ObjectType::ClassObject(manager)) =
self.document.objects.get_mut(&manager_handle)
{
manager.owner = root;
if let ClassObjectData::SectionManager(manager) = &mut manager.data {
if !manager.sections.contains(&entity_handle) {
manager.sections.push(entity_handle);
}
}
}
true
}
pub fn add_entity(&mut self, entity: EntityType) -> Handle {
self.add_entity_internal(entity, true)
}
@@ -195,6 +295,7 @@ impl Scene {
// bookkeeping and remains in place while an entity delta is undone.
let mutates_block_structure =
matches!(&entity, EntityType::Block(_) | EntityType::BlockEnd(_));
let creates_section_structure = self.prepare_section_objects(&mut entity);
let hatch_seed = if let EntityType::Hatch(dxf) = &entity {
let color = self.render_style(&entity).0;
Self::hatch_model_from_dxf(dxf, color)
@@ -298,6 +399,9 @@ impl Scene {
};
if !handle.is_null() {
if let Some(keep) = self.refedit_keep.as_mut() {
keep.insert(handle);
}
self.invalidate_dependency_index();
if let Some(model) = hatch_seed {
self.hatches.insert(handle, model);
@@ -334,7 +438,11 @@ impl Scene {
// state (a new layer, application ID, or block).
if self.is_recording_undo() {
self.record_undo_before(handle, None);
if creates_layer || creates_app_id || mutates_block_structure {
if creates_layer
|| creates_app_id
|| mutates_block_structure
|| creates_section_structure
{
self.poison_undo_recording();
}
}
@@ -2583,6 +2691,12 @@ impl Scene {
self.solid_models = HashMap::default();
*self.camera.borrow_mut() = Camera::default();
self.camera_generation += 1;
// A brand-new/replaced document has none of these yet — without
// resetting them, a "new"/"open" (e.g. via automation, which reuses
// this same reset) would leak the previous document's constraints
// and named parameters into the fresh one.
self.sketch_constraints.clear();
self.named_parameters = crate::scene::named_parameters::ParameterTable::new();
self.bump_geometry();
}
}