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

+110
View File
@@ -0,0 +1,110 @@
use acadrust::entities::{EntityCommon, ExtendedEntity, ExtendedEntityData, SectionObjectData};
use acadrust::objects::{ClassObjectData, ObjectType};
use acadrust::types::{Color, Handle, Vector3};
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
use acadrust::EntityType;
use OpenCADStudio::scene::Scene;
fn add_slice(scene: &mut Scene) -> Handle {
let mut common = EntityCommon::new();
let mut slice = ExtendedDataRecord::new("IsSlice");
slice.values = vec![XDataValue::Integer16(0), XDataValue::Integer16(1)];
common.extended_data.add_record(slice);
let mut thickness = ExtendedDataRecord::new("ThicknessDepth");
thickness.values = vec![XDataValue::Real(0.75)];
common.extended_data.add_record(thickness);
scene.add_entity(EntityType::Extended(ExtendedEntity {
common,
data: ExtendedEntityData::SectionObject(SectionObjectData {
state: 1,
flags: 5,
name: "Section Plane (1)".to_string(),
vertical_direction: Vector3::UNIT_Z,
top_height: 5.0,
bottom_height: 5.0,
indicator_alpha: 70,
indicator_color: Color::from_index(9),
vertices: vec![Vector3::ZERO, Vector3::new(10.0, 0.0, 0.0)],
back_line_vertices: Vec::new(),
settings_handle: Handle::NULL,
}),
}))
}
fn assert_section_graph(document: &acadrust::CadDocument, extension: &str) {
let (handle, section) = document
.entities()
.find_map(|entity| match entity {
EntityType::Extended(extended) => match &extended.data {
ExtendedEntityData::SectionObject(_) => Some((extended.common.handle, extended)),
_ => None,
},
_ => None,
})
.unwrap_or_else(|| panic!("section entity should round-trip through {extension}"));
let ExtendedEntityData::SectionObject(data) = &section.data else {
unreachable!();
};
assert_eq!(data.state, 1);
assert!(data.back_line_vertices.is_empty());
assert!(matches!(
section
.common
.extended_data
.get_record("IsSlice")
.map(|record| record.values.as_slice()),
Some([XDataValue::Integer16(0), XDataValue::Integer16(1)])
));
assert!(matches!(
section
.common
.extended_data
.get_record("ThicknessDepth")
.and_then(|record| record.values.first()),
Some(XDataValue::Real(value)) if (*value - 0.75).abs() < 1e-12
));
assert!(matches!(
document.objects.get(&data.settings_handle),
Some(ObjectType::ClassObject(object))
if object.owner == handle
&& matches!(&object.data, ClassObjectData::SectionSettings(_))
));
let root = document
.objects
.get(&document.header.named_objects_dict_handle)
.expect("root dictionary should round-trip");
let ObjectType::Dictionary(root) = root else {
panic!("root should be a dictionary");
};
let manager_handle = root
.entries
.iter()
.find(|(name, _)| name == "ACAD_SECTION_MANAGER")
.map(|(_, handle)| *handle)
.expect("section manager should be registered");
let Some(ObjectType::ClassObject(manager)) = document.objects.get(&manager_handle) else {
panic!("section manager handle should resolve");
};
let ClassObjectData::SectionManager(manager) = &manager.data else {
panic!("registered object should be a section manager");
};
assert_eq!(manager.sections, vec![handle]);
}
#[test]
fn section_plane_graph_survives_dxf_and_dwg_roundtrips() {
let mut scene = Scene::new();
add_slice(&mut scene);
for extension in ["dxf", "dwg"] {
let bytes =
OpenCADStudio::io::save_to_bytes(&scene.document, extension, scene.document.version)
.expect("section document should save");
let document =
OpenCADStudio::io::load_bytes(&format!("section-roundtrip.{extension}"), bytes)
.expect("section document should reload");
assert_section_graph(&document, extension);
}
}