forked from 1b-agent/opencadstudio-upstream
Port 6 audit-found bugs from the upstream OSNAP fix branch
Ports the fixes from fix/osnap-circle-arc-ellipse-intersection (and this session's audit follow-up) onto this branch. All are independent correctness/reliability fixes, unrelated to the parametric-constraint- system work also in progress here. 1. OSNAP Intersection snap used a tessellated approximation instead of exact geometry (GitHub discussion #1052): a circle/arc always tessellates at a fixed 48 segments in the resident wire set regardless of radius or zoom, so every circle/arc intersection snap was off by construction. Added exact_circle_intersections, a closed-form, frame-independent circle/arc-vs-circle/arc solver, wired into both Intersection-snap code paths ahead of the tessellated sweep. This branch's TangentGeom predates the PlanarEllipse variant added upstream, so the ellipse/line generalization from the upstream fix isn't ported here — this covers the literal reported bug (a circle-circle case) using only variants this branch already has. 2. Unbounded MINSERT allocation: array_offsets() reserved one Vec element per array instance with row/column counts parsed as unchecked u16s — a corrupt 65535x65535 pair requests ~96 GiB. Fixed with a documented instance budget (20,000), enforced at both the load-time corrupt-entity filter and a render-graph failsafe. 3. MINSERT spacing was scaled by the block's own scale: insert_instance_transform composed the offset translation before the instance transform, so x/y block scale multiplied the row/ column spacing too. Fixed by orienting the offset with rotation + OCS only, then translating the already-scaled instance transform. 4. Automation "open" skipped the finalization (block-origin normalization, raster-path resolution, source_path) and corrupt- entity purge a UI open always runs. Added io::load_bytes_finalized(path, bytes), used by automation's "open" handler, which now also reports the purge count. 5. Material maps were uploaded without checking device.limits().max_texture_dimension_2d, unlike raster-image rendering (which already tiles to that limit). A mesh material is sampled by UV, so it can't use the same tiling trick — instead, upload_rgba_texture now downscales an oversized image before upload, falling back to the 1x1 material color if malformed. 6. Sketch-constraint persistence/undo, audit-found while reviewing this branch's own constraint system: - Clearing every constraint in a scope after a prior non-empty save left the stale XRecord in place, resurrecting the deleted constraints on the next load. materialize_sketch_constraints_for_ save now actively removes the XRecord when a scope goes back to empty. - ERASE removing an entity took its constraints with it (design doc's deletion policy), but sketch_constraints lives on Scene, outside document/document.objects, so nothing captured that mutation for undo — the erased geometry came back on undo, but its constraints didn't. refresh_sketch_constraints's deletion pass now records the touched scope's before-image into the same UndoRecording the entity removal rides in, threaded through a new sketch_constraints channel on DeltaSnapshot, so one undo restores both together. HistorySnapshot::Delta is now boxed (clippy flagged the resulting size skew across undo/redo stack variants). 31 new/adapted tests across all six fixes. Full workspace suite passes except the one pre-existing, unrelated font-test failure also present on main. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
63a74b19a9
commit
44deee2ac9
11 files changed
+953
-41
No files matched your search
+45
-7
@@ -330,6 +330,15 @@ pub struct UndoRecording {
|
||||
/// by the command. `None` denotes a newly-created object.
|
||||
object_before: HashMap<Handle, Option<ObjectType>>,
|
||||
object_order: Vec<Handle>,
|
||||
/// First-touch before-images of every sketch-constraint scope a command
|
||||
/// changed (e.g. ERASE removing an entity's constraints along with it via
|
||||
/// `refresh_sketch_constraints`'s deletion policy). `sketch_constraints`
|
||||
/// lives on `Scene`, not in `document`/`document.objects`, so neither
|
||||
/// directory above ever sees this change — it needs its own directory.
|
||||
/// Unlike the two above, there is no "didn't exist before" case: a
|
||||
/// scope's `Vec` entry, once created, is never removed.
|
||||
sketch_constraints_before: HashMap<sketch_constraints::SketchScope, sketch_constraints::SketchConstraintSet>,
|
||||
sketch_constraints_order: Vec<sketch_constraints::SketchScope>,
|
||||
poisoned: bool,
|
||||
}
|
||||
|
||||
@@ -339,18 +348,22 @@ impl UndoRecording {
|
||||
self.poisoned
|
||||
}
|
||||
|
||||
/// No entity or object entry was recorded (nothing to undo).
|
||||
/// No entity, object, or sketch-constraint entry was recorded (nothing to undo).
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.order.is_empty() && self.object_order.is_empty()
|
||||
self.order.is_empty() && self.object_order.is_empty() && self.sketch_constraints_order.is_empty()
|
||||
}
|
||||
|
||||
/// Consume both recording directories in deterministic first-touch order.
|
||||
/// A `None` image means the entity/object was added by the command.
|
||||
/// Consume all three recording directories in deterministic first-touch
|
||||
/// order. A `None` image means the entity/object was added by the
|
||||
/// command; every sketch-constraint scope's before-image is a real
|
||||
/// `SketchConstraintSet` (its `Vec` entry, once created, is never
|
||||
/// removed, so there is no "didn't exist before" case there).
|
||||
pub fn into_recorded_images(
|
||||
mut self,
|
||||
) -> (
|
||||
Vec<(Handle, Option<Arc<EntityType>>)>,
|
||||
Vec<(Handle, Option<ObjectType>)>,
|
||||
Vec<(sketch_constraints::SketchScope, sketch_constraints::SketchConstraintSet)>,
|
||||
) {
|
||||
let entities = self
|
||||
.order
|
||||
@@ -362,7 +375,12 @@ impl UndoRecording {
|
||||
.drain(..)
|
||||
.map(|h| (h, self.object_before.remove(&h).flatten()))
|
||||
.collect();
|
||||
(entities, objects)
|
||||
let sketch_constraints = self
|
||||
.sketch_constraints_order
|
||||
.drain(..)
|
||||
.filter_map(|scope| self.sketch_constraints_before.remove(&scope).map(|before| (scope, before)))
|
||||
.collect();
|
||||
(entities, objects, sketch_constraints)
|
||||
}
|
||||
|
||||
/// Entity-only convenience used by the focused Scene delta tests.
|
||||
@@ -2538,6 +2556,26 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Record one sketch-constraint scope's whole-set image before its first
|
||||
/// mutation within the open recording (first touch wins) — e.g. ERASE
|
||||
/// about to remove some of a scope's constraints via
|
||||
/// `refresh_sketch_constraints`'s deletion policy. `sketch_constraints`
|
||||
/// lives on `Scene`, outside `document`/`document.objects`, so neither
|
||||
/// `record_undo_before` nor `record_undo_object_before` ever sees this
|
||||
/// change on their own.
|
||||
pub(crate) fn record_undo_sketch_constraints_before(
|
||||
&mut self,
|
||||
scope: sketch_constraints::SketchScope,
|
||||
before: sketch_constraints::SketchConstraintSet,
|
||||
) {
|
||||
if let Some(rec) = self.undo_recording.as_mut() {
|
||||
if !rec.sketch_constraints_before.contains_key(&scope) {
|
||||
rec.sketch_constraints_order.push(scope);
|
||||
rec.sketch_constraints_before.insert(scope, before);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flag the open recording as touching non-entity state (a new layer, a
|
||||
/// group edit, a `*D` block record) that a pure-entity delta cannot
|
||||
/// restore. The app's per-command predicate keeps this from firing.
|
||||
@@ -11007,7 +11045,7 @@ mod delta_undo_tests {
|
||||
let handle = scene.add_entity(EntityType::RasterImage(image));
|
||||
let rec = scene.take_undo_recording().unwrap();
|
||||
assert!(!rec.is_poisoned());
|
||||
let (entities, objects) = rec.into_recorded_images();
|
||||
let (entities, objects, _sketch_constraints) = rec.into_recorded_images();
|
||||
assert_eq!(entities.len(), 1);
|
||||
assert_eq!(entities[0].0, handle);
|
||||
assert_eq!(objects.len(), 1);
|
||||
@@ -11031,7 +11069,7 @@ mod delta_undo_tests {
|
||||
scene.erase_entities(&[h1]);
|
||||
let rec = scene.take_undo_recording().unwrap();
|
||||
assert!(!rec.is_poisoned());
|
||||
let (entities, objects) = rec.into_recorded_images();
|
||||
let (entities, objects, _sketch_constraints) = rec.into_recorded_images();
|
||||
assert_eq!(entities.len(), 1);
|
||||
assert_eq!(entities[0].0, h1);
|
||||
assert_eq!(objects.len(), 1);
|
||||
|
||||
Reference in new issue
Block a user