perf(scene): make geometry edits incremental
Avoid full document, cache, and GPU rebuilds for entity deltas so add, delete, history, selection, and live polyline edits stay responsive on large drawings.
This commit is contained in:
parent
73b8dbe9d4
commit
05b044f367
32 changed files with 1929 additions and 509 deletions
|
|
@ -351,9 +351,9 @@ impl OpenCADStudio {
|
|||
self.update_cont_anchor(&entity);
|
||||
}
|
||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||
// A plain drawable added on an existing layer touches only the
|
||||
// one new entity; a block/image/dimension/viewport add also
|
||||
// mutates layers/objects/blocks → keep the full snapshot.
|
||||
// Ordinary drawables, viewports and raster images use targeted
|
||||
// entity/object deltas. Block sentinels and novel layers retain
|
||||
// the structure snapshot fallback.
|
||||
let delta_safe = self.delta_add_safe(i, &entity);
|
||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||
self.commit_entity(entity);
|
||||
|
|
@ -401,7 +401,7 @@ impl OpenCADStudio {
|
|||
)
|
||||
});
|
||||
let label = self.history_label_from_active_cmd(i, "MVIEW");
|
||||
let pending = self.begin_undo(i, label, 1, false);
|
||||
let pending = self.begin_undo(i, label, 1, true);
|
||||
let handle = self.commit_entity_handle(
|
||||
acadrust::EntityType::Viewport(viewport),
|
||||
);
|
||||
|
|
@ -475,7 +475,7 @@ impl OpenCADStudio {
|
|||
let created_boundary = boundary.is_some();
|
||||
let touched = 2;
|
||||
let label = self.history_label_from_active_cmd(i, "MVIEW");
|
||||
let pending = self.begin_undo(i, label, touched, false);
|
||||
let pending = self.begin_undo(i, label, touched, true);
|
||||
let clip_handle = match boundary {
|
||||
Some(mut boundary) => {
|
||||
// A non-rectangular viewport owns a helper boundary
|
||||
|
|
@ -1126,6 +1126,16 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
}
|
||||
CmdResult::FinalizeLiveEntity(handle) => {
|
||||
// The live geometry already matches the command's committed
|
||||
// vertices. Close the deferred history image and UI state
|
||||
// without publishing a redundant Modified delta.
|
||||
self.finish_live_entity_history(i, handle);
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.restore_pre_cmd_tangent();
|
||||
}
|
||||
CmdResult::RemoveLiveEntity(handle) => {
|
||||
// The command backed off below a valid entity (PLINE Undo at
|
||||
// one remaining vertex): take the live entity out of the
|
||||
|
|
@ -1289,9 +1299,14 @@ impl OpenCADStudio {
|
|||
}
|
||||
self.tabs[i].dirty = true;
|
||||
// Color / linetype / lineweight are baked into the cached
|
||||
// wires at tessellation time; bump the geometry epoch so the
|
||||
// matched objects repaint instead of holding their old look.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// wires at tessellation time; re-tessellate only the matched
|
||||
// objects instead of rebuilding a large drawing.
|
||||
let changes: Vec<_> = dest
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.refresh_properties();
|
||||
self.command_line
|
||||
.push_info(&format!("Properties matched to {} object(s).", dest.len()));
|
||||
|
|
@ -1661,9 +1676,10 @@ impl OpenCADStudio {
|
|||
if changed {
|
||||
self.tabs[i].dirty = true;
|
||||
// Repaint immediately (wide-band fill included).
|
||||
self.tabs[i].scene.mark_entity_dirty(handle);
|
||||
self.tabs[i].scene.refresh_fill_model(handle);
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.command_line.push_output("PEDIT: applied.");
|
||||
self.refresh_properties();
|
||||
} else {
|
||||
|
|
@ -2093,15 +2109,11 @@ impl OpenCADStudio {
|
|||
// moved entities so the viewport reflects the stretch right away
|
||||
// instead of only on the next unrelated redraw. See #95.
|
||||
if count > 0 {
|
||||
if structural {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
} else {
|
||||
let changes: Vec<_> = changed_handles
|
||||
.iter()
|
||||
.map(|&handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
let changes: Vec<_> = changed_handles
|
||||
.iter()
|
||||
.map(|&handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
|
|
@ -2928,9 +2940,15 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
}
|
||||
// The wires were tessellated before the filters existed; force a rebuild
|
||||
// so the clip is applied to the freshly-pasted, now-filtered inserts.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// The wires were tessellated before the filters existed; refresh only
|
||||
// the freshly-pasted entities whose clip object graph was attached.
|
||||
let changes: Vec<_> = by_index
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|handle| !handle.is_null())
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
|
||||
/// Recreate the captured xdictionary subtrees in this document (fresh
|
||||
|
|
@ -3098,9 +3116,11 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
|||
};
|
||||
|
||||
let effective_spacing = if spacing <= 0.0 { 10.0 } else { spacing };
|
||||
let mut changes = Vec::new();
|
||||
for (idx, &hv) in other_vals.iter().enumerate() {
|
||||
let h = acadrust::Handle::from(hv);
|
||||
let target = base_coord + effective_spacing * (idx + 1) as f64;
|
||||
let mut changed = false;
|
||||
if let Some(acadrust::EntityType::Dimension(dim)) = scene.document.get_entity_mut(h) {
|
||||
// Slide this dim's definition point along perp so its perpendicular
|
||||
// coordinate equals `target`; update both the struct field (render)
|
||||
|
|
@ -3115,19 +3135,26 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
|||
Dimension::Linear(d) => {
|
||||
slide(&mut d.definition_point);
|
||||
d.base.definition_point = d.definition_point;
|
||||
changed = true;
|
||||
}
|
||||
Dimension::Aligned(d) => {
|
||||
slide(&mut d.definition_point);
|
||||
d.base.definition_point = d.definition_point;
|
||||
changed = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// The dimension line moved, so its baked *D block is stale — drop it so
|
||||
// the next save re-bakes it (no-op for non-dimensions). (#181)
|
||||
scene.invalidate_dim_block_recorded(h);
|
||||
if changed {
|
||||
// The dimension line moved, so its baked *D block is stale — drop it
|
||||
// so the next save re-bakes it. (#181)
|
||||
scene.invalidate_dim_block_recorded(h);
|
||||
changes.push((h, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
if !changes.is_empty() {
|
||||
scene.bump_entities(&changes);
|
||||
}
|
||||
scene.bump_geometry();
|
||||
}
|
||||
|
||||
// ── MLEADERALIGN helper ───────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ impl OpenCADStudio {
|
|||
.unwrap_or_default();
|
||||
self.push_undo_snapshot(i, "ATTSYNC");
|
||||
let mut synced = 0usize;
|
||||
let mut changes = Vec::new();
|
||||
for e in self.tabs[i].scene.document.entities_mut() {
|
||||
if let acadrust::EntityType::Insert(ins) = e {
|
||||
if ins.block_name.eq_ignore_ascii_case(&block) {
|
||||
|
|
@ -390,10 +391,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
synced += 1;
|
||||
changes.push((ins.common.handle, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!(
|
||||
"ATTSYNC: synchronised {synced} insert(s) of \"{block}\" against {} attribute definition(s).",
|
||||
|
|
|
|||
|
|
@ -169,7 +169,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line
|
||||
.push_output(&format!("JUSTIFYTEXT: updated {n} text object(s)."));
|
||||
}
|
||||
|
|
@ -253,7 +257,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line
|
||||
.push_output(&format!("TCASE: updated {n} text object(s)."));
|
||||
}
|
||||
|
|
@ -397,7 +405,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line.push_output(&format!(
|
||||
"TEXTFIT: fitted {n} text object(s) to width {target}."
|
||||
));
|
||||
|
|
@ -459,7 +471,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
let changes: Vec<_> = texts
|
||||
.iter()
|
||||
.map(|(handle, _, _)| (*handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line.push_output(&format!(
|
||||
"TCOUNT: numbered {} text object(s) from {start}.",
|
||||
texts.len()
|
||||
|
|
|
|||
|
|
@ -686,7 +686,11 @@ impl OpenCADStudio {
|
|||
n += 1;
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!(
|
||||
"OBJECTSCALE: marked {n} object(s) annotative (they scale with the annotation scale)."
|
||||
|
|
@ -776,6 +780,7 @@ impl OpenCADStudio {
|
|||
let v = v.min(100);
|
||||
self.push_undo_snapshot(i, "ADJUST");
|
||||
let mut changed = 0usize;
|
||||
let mut changed_handles = Vec::new();
|
||||
for h in &handles {
|
||||
if let Some(acadrust::EntityType::RasterImage(img)) = self.tabs[i]
|
||||
.scene
|
||||
|
|
@ -787,14 +792,17 @@ impl OpenCADStudio {
|
|||
"BRIGHTNESS" => {
|
||||
img.brightness = v;
|
||||
changed += 1;
|
||||
changed_handles.push(*h);
|
||||
}
|
||||
"CONTRAST" => {
|
||||
img.contrast = v;
|
||||
changed += 1;
|
||||
changed_handles.push(*h);
|
||||
}
|
||||
"FADE" => {
|
||||
img.fade = v;
|
||||
changed += 1;
|
||||
changed_handles.push(*h);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -802,7 +810,14 @@ impl OpenCADStudio {
|
|||
}
|
||||
if changed > 0 {
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
for &handle in &changed_handles {
|
||||
self.tabs[i].scene.reseed_derived_caches(handle);
|
||||
}
|
||||
let changes: Vec<_> = changed_handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line
|
||||
.push_output(&format!("ADJUST: {action} = {v} on {changed} image(s)."));
|
||||
} else {
|
||||
|
|
@ -860,7 +875,7 @@ impl OpenCADStudio {
|
|||
let hdr = &mut self.tabs[i].scene.document.header;
|
||||
hdr.current_annotation_scale = arg.clone();
|
||||
hdr.annotation_scale_value = 1.0 / v as f64;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_annotation_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(&format!("Annotation scale: {arg}"));
|
||||
|
|
@ -1014,7 +1029,6 @@ impl OpenCADStudio {
|
|||
self.tabs[i]
|
||||
.scene
|
||||
.add_entity_clone(acadrust::EntityType::Table(table));
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!(
|
||||
"DATALINK: imported {nrows}×{ncols} cells into a table at the origin."
|
||||
|
|
@ -1061,7 +1075,6 @@ impl OpenCADStudio {
|
|||
.scene
|
||||
.add_entity_clone(acadrust::EntityType::Point(p));
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!(
|
||||
"LANDXMLIMPORT: imported {} survey point(s). Use ZOOM EXTENTS to view.",
|
||||
|
|
|
|||
|
|
@ -453,7 +453,9 @@ impl OpenCADStudio {
|
|||
self.grip_hover = None;
|
||||
self.grip_popup = None;
|
||||
self.visibility_popup = None;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// Entering BEDIT changes which block is assembled, not the
|
||||
// cached geometry of that block's entities.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
// Frame the camera on the block's own geometry (block-local, near
|
||||
// origin) — fit_all() goes through current_layout_block_handle so
|
||||
// it already scopes to the edited block. Without this the view
|
||||
|
|
|
|||
|
|
@ -129,9 +129,14 @@ impl OpenCADStudio {
|
|||
self.push_undo_snapshot(i, "LAYER COLOR");
|
||||
self.tabs[i].dirty = true;
|
||||
// By-layer colour is baked into every wire on
|
||||
// this layer — re-tessellate so they repaint
|
||||
// immediately (issue #231 class).
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// this layer. The dependency index maps block
|
||||
// children back to their top-level INSERTs, so
|
||||
// unrelated entities stay warm.
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_layer_dependencies(std::slice::from_ref(
|
||||
&layer_name,
|
||||
));
|
||||
self.command_line.push_output(&format!(
|
||||
"LAYER: '{}' color set to ACI {}.",
|
||||
layer_name, idx
|
||||
|
|
|
|||
|
|
@ -200,7 +200,10 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
self.tabs[i].scene.document.layers.remove(&src);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_dependency_index();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_layer_dependencies(std::slice::from_ref(&dst));
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_output(&format!(
|
||||
|
|
|
|||
|
|
@ -725,7 +725,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line.push_output(&format!(
|
||||
"SETBYLAYER: reset {changed} entity/entities to ByLayer."
|
||||
));
|
||||
|
|
@ -1593,7 +1597,9 @@ impl OpenCADStudio {
|
|||
// TEXTFILL reset the glyph atlas; re-tessellate so text picks
|
||||
// up the re-baked filled / hollow tiles.
|
||||
if name == "TEXTFILL" {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_text_geometry_dependencies();
|
||||
}
|
||||
// ORTHOMODE / OSMODE set the header directly; mirror them into
|
||||
// the live Ortho / running OSNAP so the constraint + status
|
||||
|
|
@ -1902,7 +1908,7 @@ impl OpenCADStudio {
|
|||
self.push_undo_snapshot(i, "PDMODE");
|
||||
self.tabs[i].scene.document.header.point_display_mode = v;
|
||||
// Point glyphs are built at tessellation time — rebuild them.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!("PDMODE set to {v}"));
|
||||
} else {
|
||||
|
|
@ -2046,7 +2052,7 @@ impl OpenCADStudio {
|
|||
} else if let Ok(v) = val_str.parse::<f64>() {
|
||||
self.push_undo_snapshot(i, "PDSIZE");
|
||||
self.tabs[i].scene.document.header.point_display_size = v;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(&format!("PDSIZE set to {v:.4}"));
|
||||
|
|
|
|||
|
|
@ -956,7 +956,9 @@ impl OpenCADStudio {
|
|||
// render-side `sort_cache` rebuilds per geometry epoch.
|
||||
// Bump it so the new draw order shows immediately
|
||||
// instead of waiting for an unrelated geometry change.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// Draw order changes submission order only; all
|
||||
// per-entity tessellation remains valid.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.tabs[i].dirty = true;
|
||||
} else {
|
||||
self.command_line.push_info(
|
||||
|
|
|
|||
|
|
@ -623,52 +623,30 @@ impl OpenCADStudio {
|
|||
self.push_undo_entry(i, HistorySnapshot::Delta(delta));
|
||||
}
|
||||
|
||||
/// Copy is delta-safe only when no target is a Dimension and no complete
|
||||
/// group is copied: dimensions clone fresh anonymous `*D` block records,
|
||||
/// and complete group copies add Group objects / dictionary entries. Both
|
||||
/// are non-entity state a pure-entity delta can't restore.
|
||||
/// Dimension copies still need a full snapshot because they clone a fresh
|
||||
/// anonymous `*D` block record. Complete group copies record their exact
|
||||
/// Group/dictionary object entries and remain delta-safe.
|
||||
pub(super) fn delta_copy_safe(&self, i: usize, handles: &[Handle]) -> bool {
|
||||
use acadrust::objects::ObjectType;
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let handle_set: HashSet<Handle> = handles.iter().copied().collect();
|
||||
let copies_dimension = handles
|
||||
!handles
|
||||
.iter()
|
||||
.any(|h| matches!(doc.get_entity(*h), Some(EntityType::Dimension(_))));
|
||||
let copies_complete_group = doc.objects.values().any(|o| match o {
|
||||
ObjectType::Group(g) => {
|
||||
!g.entities.is_empty() && g.entities.iter().all(|h| handle_set.contains(h))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
!copies_dimension && !copies_complete_group
|
||||
.any(|h| matches!(doc.get_entity(*h), Some(EntityType::Dimension(_))))
|
||||
}
|
||||
|
||||
/// Erase is delta-safe only when no target belongs to a group: erasing a
|
||||
/// grouped entity rewrites the group's membership in `document.objects`.
|
||||
pub(super) fn delta_erase_safe(&self, i: usize, handles: &[Handle]) -> bool {
|
||||
use acadrust::objects::ObjectType;
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let handle_set: HashSet<Handle> = handles.iter().copied().collect();
|
||||
!doc.objects.values().any(|o| match o {
|
||||
ObjectType::Group(g) => g.entities.iter().any(|h| handle_set.contains(h)),
|
||||
_ => false,
|
||||
})
|
||||
/// Erase records any affected Group/object dictionary entries alongside the
|
||||
/// entity before-images, so it never needs a whole-structure snapshot.
|
||||
pub(super) fn delta_erase_safe(&self, _i: usize, _handles: &[Handle]) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Add is delta-safe only for a plain drawable on an already-existing layer:
|
||||
/// a block / image add also creates block records, image definitions or
|
||||
/// layers (non-entity state). INSERT only references an existing block and
|
||||
/// is entity-delta safe.
|
||||
/// Add is delta-safe on an already-existing layer unless it creates block
|
||||
/// records. RasterImage definitions are captured as exact object-map deltas;
|
||||
/// Viewport commits explicitly record their added entity handle.
|
||||
pub(super) fn delta_add_safe(&self, i: usize, entity: &EntityType) -> bool {
|
||||
if matches!(
|
||||
entity,
|
||||
EntityType::Block(_)
|
||||
| EntityType::BlockEnd(_)
|
||||
| EntityType::RasterImage(_)
|
||||
// A viewport commit routes through add_entity_to_layout +
|
||||
// bump_geometry_no_blocks, bypassing the recorded Scene::add_entity
|
||||
// — nothing would be captured, so keep the full snapshot.
|
||||
| EntityType::Viewport(_)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -706,14 +684,25 @@ impl OpenCADStudio {
|
|||
pending.label
|
||||
);
|
||||
}
|
||||
let entities: Vec<(Handle, Option<Arc<EntityType>>, Option<Arc<EntityType>>)> = rec
|
||||
.into_before_images()
|
||||
let (entity_before, object_before) = rec.into_recorded_images();
|
||||
let entities: Vec<(Handle, Option<Arc<EntityType>>, Option<Arc<EntityType>>)> = entity_before
|
||||
.into_iter()
|
||||
.map(|(h, before)| {
|
||||
let after = self.tabs[i].scene.document.get_entity_arc(h);
|
||||
(h, before, after)
|
||||
})
|
||||
.collect();
|
||||
let objects: Vec<ObjectEntryDelta> = object_before
|
||||
.into_iter()
|
||||
.filter_map(|(handle, before)| {
|
||||
let after = self.tabs[i].scene.document.objects.get(&handle).cloned();
|
||||
(before != after).then_some(ObjectEntryDelta {
|
||||
handle,
|
||||
before,
|
||||
after,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let selected_after = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
let dirty_after = self.tabs[i].dirty;
|
||||
let mut structure = pending.structure_before.map(StructureSnapshot::Full);
|
||||
|
|
@ -722,20 +711,23 @@ impl OpenCADStudio {
|
|||
let after_structure = self.tabs[i].scene.document.snapshot_structure();
|
||||
let added_handles: Vec<Handle> = entities
|
||||
.iter()
|
||||
.filter_map(|(handle, before, after)| {
|
||||
(before.is_none() && after.is_some()).then_some(*handle)
|
||||
})
|
||||
.collect();
|
||||
acadrust::CadDocument::align_added_entity_structure(
|
||||
before_structure,
|
||||
&after_structure,
|
||||
&added_handles,
|
||||
);
|
||||
if *before_structure == after_structure {
|
||||
structure = None;
|
||||
}
|
||||
.filter_map(|(handle, before, after)| {
|
||||
(before.is_none() && after.is_some()).then_some(*handle)
|
||||
})
|
||||
.collect();
|
||||
acadrust::CadDocument::align_added_entity_structure(
|
||||
before_structure,
|
||||
&after_structure,
|
||||
&added_handles,
|
||||
);
|
||||
if *before_structure == after_structure {
|
||||
structure = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if structure.is_none() && !objects.is_empty() {
|
||||
structure = Some(StructureSnapshot::Objects(objects));
|
||||
}
|
||||
let delta = DeltaSnapshot {
|
||||
entities,
|
||||
current_layout_before: pending.current_layout,
|
||||
|
|
@ -849,7 +841,6 @@ impl OpenCADStudio {
|
|||
self.tabs[i].scene.document.objects.remove(&entry.handle);
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.invalidate_dependency_index();
|
||||
}
|
||||
StructureSnapshot::Styles {
|
||||
before,
|
||||
|
|
@ -909,13 +900,21 @@ impl OpenCADStudio {
|
|||
(&snapshot.after, &snapshot.selected_after)
|
||||
};
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
let mut affected = scene.object_isolation.hidden.clone();
|
||||
affected.extend(state.hidden.iter().copied());
|
||||
scene.object_isolation = state.clone();
|
||||
scene.selected = selected
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|handle| scene.document.get_entity(*handle).is_some())
|
||||
.collect();
|
||||
scene.bump_geometry_no_blocks();
|
||||
let changes: Vec<_> = affected
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
if !changes.is_empty() {
|
||||
scene.bump_entities(&changes);
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the one expensive cache/UI synchronization required after a
|
||||
|
|
@ -925,7 +924,7 @@ impl OpenCADStudio {
|
|||
&mut self,
|
||||
i: usize,
|
||||
had_full: bool,
|
||||
structure_changed: bool,
|
||||
layer_panel_changed: bool,
|
||||
changes: &[(Handle, crate::scene::ChangeKind)],
|
||||
) {
|
||||
self.tabs[i].edit_revision = self.tabs[i].edit_revision.wrapping_add(1);
|
||||
|
|
@ -970,7 +969,7 @@ impl OpenCADStudio {
|
|||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.tabs[i].active_grip = None;
|
||||
if structure_changed {
|
||||
if layer_panel_changed {
|
||||
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
||||
let vp_info = self.tabs[i].scene.viewport_list();
|
||||
self.tabs[i]
|
||||
|
|
@ -1001,7 +1000,7 @@ impl OpenCADStudio {
|
|||
|
||||
let mut last_label = String::new();
|
||||
let mut had_full = false;
|
||||
let mut structure_changed = false;
|
||||
let mut layer_panel_changed = false;
|
||||
let mut changes = Vec::new();
|
||||
for _ in 0..steps {
|
||||
let Some(snapshot) = self.tabs[i].history.undo_stack.pop() else {
|
||||
|
|
@ -1013,7 +1012,19 @@ impl OpenCADStudio {
|
|||
// Symmetric: undo applies the before side, then the same
|
||||
// delta rides to the redo stack (it still holds the after
|
||||
// side) — no current-state capture needed.
|
||||
structure_changed |= d.structure.is_some();
|
||||
layer_panel_changed |= d.structure.as_ref().is_some_and(|structure| {
|
||||
matches!(
|
||||
structure,
|
||||
StructureSnapshot::Full(_) | StructureSnapshot::Layers(_)
|
||||
)
|
||||
}) || d.entities.iter().any(|(_, before, after)| {
|
||||
before
|
||||
.as_deref()
|
||||
.is_some_and(|entity| matches!(entity, EntityType::Viewport(_)))
|
||||
|| after
|
||||
.as_deref()
|
||||
.is_some_and(|entity| matches!(entity, EntityType::Viewport(_)))
|
||||
});
|
||||
had_full |= d.structure.as_ref().is_some_and(StructureSnapshot::is_full);
|
||||
changes.extend(self.apply_delta_state(i, &mut d, true));
|
||||
self.tabs[i]
|
||||
|
|
@ -1030,7 +1041,7 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.finish_history_apply(i, had_full, structure_changed, &changes);
|
||||
self.finish_history_apply(i, had_full, layer_panel_changed, &changes);
|
||||
self.command_line
|
||||
.push_output(&format!("Undo: {last_label}"));
|
||||
}
|
||||
|
|
@ -1047,7 +1058,7 @@ impl OpenCADStudio {
|
|||
|
||||
let mut last_label = String::new();
|
||||
let mut had_full = false;
|
||||
let mut structure_changed = false;
|
||||
let mut layer_panel_changed = false;
|
||||
let mut changes = Vec::new();
|
||||
for _ in 0..steps {
|
||||
let Some(snapshot) = self.tabs[i].history.redo_stack.pop() else {
|
||||
|
|
@ -1056,7 +1067,19 @@ impl OpenCADStudio {
|
|||
last_label = snapshot.label().to_string();
|
||||
match snapshot {
|
||||
HistorySnapshot::Delta(mut d) => {
|
||||
structure_changed |= d.structure.is_some();
|
||||
layer_panel_changed |= d.structure.as_ref().is_some_and(|structure| {
|
||||
matches!(
|
||||
structure,
|
||||
StructureSnapshot::Full(_) | StructureSnapshot::Layers(_)
|
||||
)
|
||||
}) || d.entities.iter().any(|(_, before, after)| {
|
||||
before
|
||||
.as_deref()
|
||||
.is_some_and(|entity| matches!(entity, EntityType::Viewport(_)))
|
||||
|| after
|
||||
.as_deref()
|
||||
.is_some_and(|entity| matches!(entity, EntityType::Viewport(_)))
|
||||
});
|
||||
had_full |= d.structure.as_ref().is_some_and(StructureSnapshot::is_full);
|
||||
changes.extend(self.apply_delta_state(i, &mut d, false));
|
||||
self.tabs[i]
|
||||
|
|
@ -1073,7 +1096,7 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.finish_history_apply(i, had_full, structure_changed, &changes);
|
||||
self.finish_history_apply(i, had_full, layer_panel_changed, &changes);
|
||||
self.command_line
|
||||
.push_output(&format!("Redo: {last_label}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1089,7 +1089,9 @@ impl super::OpenCADStudio {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
} else {
|
||||
// Align new MText to the active UCS (text runs along the UCS X axis).
|
||||
|
|
@ -1140,7 +1142,9 @@ impl super::OpenCADStudio {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
} else {
|
||||
mt.rotation = self.tabs[i].ucs_rotation_angle();
|
||||
|
|
|
|||
|
|
@ -1965,15 +1965,14 @@ impl OpenCADStudio {
|
|||
Ok(new_handle) => {
|
||||
if self.tabs[i].scene.is_recording_undo() {
|
||||
self.tabs[i].scene.record_undo_before(new_handle, None);
|
||||
self.tabs[i].scene.poison_undo_recording();
|
||||
}
|
||||
self.tabs[i].scene.auto_fit_viewport(new_handle);
|
||||
// Adding a viewport straight onto the document layout
|
||||
// bypasses Scene::add_entity, which is what normally
|
||||
// invalidates the wire-tessellation cache. Without this the
|
||||
// new viewport's border isn't tessellated until the next
|
||||
// zoom/pan forces a rebuild.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
// bypasses Scene::add_entity; publish the exact new handle
|
||||
// so only its border is tessellated.
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(new_handle, crate::scene::ChangeKind::Added)]);
|
||||
Some(new_handle)
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
|
|||
|
|
@ -581,10 +581,7 @@ impl OpenCADStudio {
|
|||
pub(super) fn style_stage_commit(&mut self) {
|
||||
let i = self.active_tab;
|
||||
let Some(stage) = self.style_stage.take() else {
|
||||
// No active transaction — still refresh the drawing so a bare Apply
|
||||
// is harmless.
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// No staged edits: a bare Apply has no geometry work to publish.
|
||||
self.sync_ribbon_styles();
|
||||
return;
|
||||
};
|
||||
|
|
@ -820,7 +817,7 @@ impl OpenCADStudio {
|
|||
if !resolves {
|
||||
self.tabs[i].scene.document.header.current_annotation_scale = snap.current.clone();
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_annotation_dependencies();
|
||||
}
|
||||
|
||||
/// Begin a staging transaction for a freshly-opened scale manager.
|
||||
|
|
|
|||
|
|
@ -206,7 +206,9 @@ impl super::OpenCADStudio {
|
|||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(h) {
|
||||
write_text_field(entity, ed.field, ed.value.clone());
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
} else {
|
||||
let mut t = Text::with_value(
|
||||
|
|
|
|||
|
|
@ -199,7 +199,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_dim_block_recorded(pending.handle);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_entities(&[(
|
||||
pending.handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
|
|
@ -534,7 +537,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
crate::scene::model::object::GripMenuAction::RemoveLeader,
|
||||
);
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.refresh_selected_grips();
|
||||
}
|
||||
// Cancel an in-progress grip drag: restore the edited
|
||||
|
|
@ -649,7 +654,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.tabs[i]
|
||||
.layers
|
||||
.sync_with_viewports(&doc_layers, vp_info);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// Per-viewport layer visibility changes the resident
|
||||
// assembly, not any entity's tessellated geometry.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1022,7 +1029,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_dim_block_recorded(popup.handle);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(popup.handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
|
|
@ -1534,7 +1543,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
.get(&value)
|
||||
.map(|br| br.name.clone());
|
||||
if let Some(canon) = canon {
|
||||
let mut changed = false;
|
||||
let mut changes = Vec::new();
|
||||
for &handle in &handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
|
|
@ -1544,14 +1553,15 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
{
|
||||
if ins.block_name != canon {
|
||||
ins.block_name = canon.clone();
|
||||
changed = true;
|
||||
changes.push((
|
||||
handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
// The picked block may not be in the defn cache
|
||||
// yet — rebuild block definitions too.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
if !changes.is_empty() {
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
}
|
||||
} else if field == "plot_style" {
|
||||
|
|
@ -1804,7 +1814,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
if protected {
|
||||
return;
|
||||
}
|
||||
let mut changed = false;
|
||||
let mut changes = Vec::new();
|
||||
for &handle in handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
|
|
@ -1814,12 +1824,12 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
{
|
||||
if ins.block_name != canon {
|
||||
ins.block_name = canon.clone();
|
||||
changed = true;
|
||||
changes.push((handle, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
if !changes.is_empty() {
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1922,9 +1922,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
}
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
// The paper sheet fill is cached; bump geometry so the new
|
||||
// sheet size re-tessellates and shows immediately.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// The paper sheet fill is cached by epoch, while document
|
||||
// entity tessellation is unaffected by the paper size.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.command_line.push_info(&format!(
|
||||
"Page setup: {w:.1}×{h:.1} mm area={plot_area} \
|
||||
center={center} rot={rotation}°"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,22 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
fn perf_message_label(msg: &Message) -> &'static str {
|
||||
match msg {
|
||||
Message::ViewportLeftPress | Message::PanePress(_) => "pointer-down",
|
||||
Message::ViewportLeftRelease | Message::PaneRelease(_) => "pointer-up",
|
||||
Message::ViewportMove(_) | Message::PaneMove(_, _) => "pointer-move",
|
||||
Message::CommandFinalize => "command-finalize",
|
||||
Message::CommandEscape => "command-escape",
|
||||
Message::Undo | Message::UndoMany(_) => "undo",
|
||||
Message::Redo | Message::RedoMany(_) => "redo",
|
||||
Message::DeleteSelected => "delete",
|
||||
Message::HoverDwellTick => "hover-dwell",
|
||||
Message::InteractionIndexReady { .. } => "interaction-index-ready",
|
||||
_ => "other",
|
||||
}
|
||||
}
|
||||
|
||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_DRAW_PX;
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
|
|
@ -139,6 +155,8 @@ impl OpenCADStudio {
|
|||
}
|
||||
|
||||
pub fn update(&mut self, msg: Message) -> Task<Message> {
|
||||
let perf_started = crate::perf::enabled().then(Instant::now);
|
||||
let perf_label = perf_message_label(&msg);
|
||||
// A modal dialog must capture the keyboard the same way it already
|
||||
// captures the mouse. Otherwise keystrokes from the global key
|
||||
// subscription leak past the modal into the command line and fire as
|
||||
|
|
@ -185,6 +203,15 @@ impl OpenCADStudio {
|
|||
self.snapper.clear_tracking();
|
||||
self.otrack_active = None;
|
||||
}
|
||||
if let Some(started) = perf_started {
|
||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||
if elapsed_ms >= 5.0 {
|
||||
crate::perf_record!(
|
||||
"[perf] update {:>7.1}ms message={perf_label}",
|
||||
elapsed_ms,
|
||||
);
|
||||
}
|
||||
}
|
||||
task
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +250,7 @@ impl OpenCADStudio {
|
|||
crate::scene::text::web_font::insert(script, Some(bytes));
|
||||
crate::scene::text::ttf_glyph::clear_fallback_cache();
|
||||
for tab in self.tabs.iter_mut() {
|
||||
tab.scene.bump_geometry();
|
||||
tab.scene.invalidate_text_geometry_dependencies();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -965,7 +992,9 @@ impl OpenCADStudio {
|
|||
&self.tabs[idx].scene.document.header.code_page,
|
||||
) {
|
||||
crate::scene::text::ttf_glyph::clear_fallback_cache();
|
||||
self.tabs[idx].scene.bump_geometry();
|
||||
self.tabs[idx]
|
||||
.scene
|
||||
.invalidate_text_geometry_dependencies();
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
|
|
@ -2103,7 +2132,7 @@ impl OpenCADStudio {
|
|||
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
|
||||
tab.scene.annotation_scale = scale;
|
||||
util::sync_annotation_scale_header(&mut tab.scene);
|
||||
tab.scene.bump_geometry();
|
||||
tab.scene.invalidate_annotation_dependencies();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -2341,7 +2370,7 @@ impl OpenCADStudio {
|
|||
self.tabs[i].scene.document.header.annotation_scale_value = p / d;
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_annotation_dependencies();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl OpenCADStudio {
|
|||
let next = if self.point_size_relative { -mag } else { mag };
|
||||
self.push_undo_snapshot(i, "PDSIZE");
|
||||
self.tabs[i].scene.document.header.point_display_size = next;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ impl OpenCADStudio {
|
|||
}
|
||||
self.push_undo_snapshot(i, "PDMODE");
|
||||
self.tabs[i].scene.document.header.point_display_mode = next;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ impl OpenCADStudio {
|
|||
self.tabs[i].wireframe = wf;
|
||||
self.ribbon.set_wireframe(wf);
|
||||
self.tabs[i].visual_style = label.into();
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
}
|
||||
|
||||
/// Project a pane-local cursor onto the active drawing plane and return a
|
||||
|
|
@ -405,7 +405,7 @@ impl OpenCADStudio {
|
|||
// viewport, the picker drives that viewport entity's own
|
||||
// render mode; the model-layout tab style is untouched.
|
||||
if self.tabs[i].scene.set_active_viewport_render_mode(mode) {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.command_line
|
||||
.push_output(&format!("Viewport visual style: {label}"));
|
||||
return Task::none();
|
||||
|
|
@ -423,7 +423,7 @@ impl OpenCADStudio {
|
|||
self.tabs[i].visual_style = label.into();
|
||||
// Re-upload face3d fills on the next frame — the render
|
||||
// pipeline keys its upload cache off `geometry_epoch`.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.command_line
|
||||
.push_output(&format!("Visual style: {label}"));
|
||||
Task::none()
|
||||
|
|
|
|||
|
|
@ -1116,7 +1116,7 @@ impl OpenCADStudio {
|
|||
s.last_tess_wires.get(),
|
||||
s.geometry_epoch,
|
||||
);
|
||||
let trace = crate::perf::snapshot_text();
|
||||
let trace = crate::perf::snapshot_tail_text(80);
|
||||
let trace = if trace.is_empty() {
|
||||
"No samples yet".to_string()
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue