forked from 1b-agent/opencadstudio-upstream
fix(hatch): preview pattern grip drag
This commit is contained in:
1 parent
a36e0d7fdc
commit
25166b9508
5 files changed
+197
No files matched your search
@@ -865,6 +865,12 @@ impl OpenCADStudio {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.apply_grip(grip.handle, grip.grip_id, apply);
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(grip.handle),
|
||||
Some(acadrust::EntityType::Hatch(_))
|
||||
) {
|
||||
self.tabs[i].scene.set_preview_hatch(grip.handle);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped;
|
||||
let apply_ms = apply_started.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
@@ -1536,6 +1536,10 @@ pub struct Scene {
|
||||
pub selection_filter: HashSet<String>,
|
||||
/// In-progress preview wires while a command is active (rubber-band + object ghosts).
|
||||
pub preview_wires: Vec<WireModel>,
|
||||
/// Live hatch fill used by interactive edits such as dragging the pattern
|
||||
/// origin grip. Kept separate from the resident hatch set so moving one
|
||||
/// pattern never re-uploads every hatch in a large drawing.
|
||||
pub preview_hatches: Arc<Vec<HatchModel>>,
|
||||
/// In-progress preview SDF glyph quads (grip drag / command preview). Rides
|
||||
/// a per-frame GPU buffer separate from the epoch-cached base text so the
|
||||
/// dragged text stays visible while it's hidden from the base set (#316).
|
||||
@@ -1911,6 +1915,7 @@ impl Scene {
|
||||
transparency_display: true,
|
||||
selection_filter: HashSet::default(),
|
||||
preview_wires: vec![],
|
||||
preview_hatches: Arc::new(Vec::new()),
|
||||
preview_text: vec![],
|
||||
interim_wire: None,
|
||||
camera_generation: 0,
|
||||
|
||||
@@ -257,6 +257,10 @@ pub struct Pipeline {
|
||||
gpu_hatch: Option<hatch_gpu::HatchGpu>,
|
||||
/// Compatibility hatch fills — one texture-backed GPU object per hatch.
|
||||
gpu_hatches_compat: Vec<hatch_web_gpu::HatchWebGpu>,
|
||||
/// Tiny live hatch batch used by grip previews. Separate from the resident
|
||||
/// batch so an interactive edit uploads only the hatch being changed.
|
||||
gpu_preview_hatch: Option<hatch_gpu::HatchGpu>,
|
||||
gpu_preview_hatches_compat: Vec<hatch_web_gpu::HatchWebGpu>,
|
||||
/// Wipeout masks — solid fills rendered after wires in a separate pass via
|
||||
/// the legacy per-primitive `WipeoutGpu` renderer.
|
||||
gpu_wipeouts: Vec<WipeoutGpu>,
|
||||
@@ -293,6 +297,7 @@ pub struct Pipeline {
|
||||
/// Arc makes pointer identity ABA-safe: an unchanged category reuses the
|
||||
/// same Arc even when an unrelated entity advances `geometry_epoch`.
|
||||
pub cached_hatch_source: Option<std::sync::Arc<Vec<HatchModel>>>,
|
||||
pub cached_preview_hatch_source: Option<std::sync::Arc<Vec<HatchModel>>>,
|
||||
pub cached_wipeout_source: Option<std::sync::Arc<Vec<HatchModel>>>,
|
||||
pub cached_image_source: Option<std::sync::Arc<Vec<ImageModel>>>,
|
||||
pub cached_text_source: Option<std::sync::Arc<Vec<text_gpu::TextVertex>>>,
|
||||
@@ -1827,6 +1832,8 @@ impl Pipeline {
|
||||
gpu_preview_wires: vec![],
|
||||
gpu_hatch: None,
|
||||
gpu_hatches_compat: vec![],
|
||||
gpu_preview_hatch: None,
|
||||
gpu_preview_hatches_compat: vec![],
|
||||
gpu_wipeouts: vec![],
|
||||
wipeout_skip_flags: vec![],
|
||||
gpu_images: vec![],
|
||||
@@ -1842,6 +1849,7 @@ impl Pipeline {
|
||||
gpu_face3d_edges: vec![],
|
||||
viewcube,
|
||||
cached_hatch_source: None,
|
||||
cached_preview_hatch_source: None,
|
||||
cached_wipeout_source: None,
|
||||
cached_image_source: None,
|
||||
cached_text_source: None,
|
||||
@@ -2877,6 +2885,35 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_preview_hatches(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
hatches: &[HatchModel],
|
||||
) {
|
||||
if let Some(bgl1) = &self.hatch_compat_bgl1 {
|
||||
self.gpu_preview_hatches_compat = hatches
|
||||
.iter()
|
||||
.filter(|h| h.boundary.len() >= 3)
|
||||
.map(|h| hatch_web_gpu::HatchWebGpu::new(device, queue, h, bgl1))
|
||||
.collect();
|
||||
self.gpu_preview_hatch = None;
|
||||
return;
|
||||
}
|
||||
|
||||
self.gpu_preview_hatches_compat.clear();
|
||||
let Some(bgl1) = &self.hatch_bgl1 else {
|
||||
self.gpu_preview_hatch = None;
|
||||
return;
|
||||
};
|
||||
let renderable: Vec<HatchModel> = hatches
|
||||
.iter()
|
||||
.filter(|h| h.boundary.len() >= 3)
|
||||
.cloned()
|
||||
.collect();
|
||||
self.gpu_preview_hatch = hatch_gpu::HatchGpu::build(device, bgl1, &renderable);
|
||||
}
|
||||
|
||||
pub fn upload_wipeouts(&mut self, device: &wgpu::Device, wipeouts: &[HatchModel]) {
|
||||
self.gpu_wipeouts = wipeouts
|
||||
.iter()
|
||||
@@ -3063,6 +3100,18 @@ impl Pipeline {
|
||||
pass.draw(0..batch.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
if let (Some(batch), Some(pipeline)) =
|
||||
(&self.gpu_preview_hatch, &self.hatch_pipeline)
|
||||
{
|
||||
if !self.skip_hatch_frame {
|
||||
pass.set_pipeline(pipeline);
|
||||
pass.set_stencil_reference(stencil_ref);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_bind_group(1, &batch.bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, batch.vertex_buffer.slice(..));
|
||||
pass.draw(0..batch.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
// Storage-free compatibility path. Draw before wires so outlines
|
||||
// remain on top, matching the batched path.
|
||||
@@ -3076,6 +3125,11 @@ impl Pipeline {
|
||||
pass.set_vertex_buffer(0, hatch.vertex_buffer.slice(..));
|
||||
pass.draw(0..6, 0..1);
|
||||
}
|
||||
for hatch in &self.gpu_preview_hatches_compat {
|
||||
pass.set_bind_group(1, &hatch.bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, hatch.vertex_buffer.slice(..));
|
||||
pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,58 @@ impl Scene {
|
||||
self.preview_wires = wires;
|
||||
}
|
||||
|
||||
/// Publish the current cached hatch as a one-entity live fill overlay.
|
||||
/// The edited hatch is hidden from the resident set during a grip drag;
|
||||
/// this keeps its pattern visible without rebuilding the full hatch batch.
|
||||
pub fn set_preview_hatch(&mut self, handle: Handle) {
|
||||
let Some(mut model) = self.hatches.get(&handle).cloned() else {
|
||||
self.preview_hatches = std::sync::Arc::new(Vec::new());
|
||||
return;
|
||||
};
|
||||
let Some(entity) = self.document.get_entity(handle) else {
|
||||
self.preview_hatches = std::sync::Arc::new(Vec::new());
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
model.pattern,
|
||||
crate::scene::model::hatch_model::HatchPattern::Gradient { .. }
|
||||
) {
|
||||
model.color = self.render_style(entity).0;
|
||||
}
|
||||
if self.selected.contains(&handle) {
|
||||
model.color = [0.15, 0.55, 1.00, model.color[3]];
|
||||
}
|
||||
model.draw_depth = self
|
||||
.draw_depth_map()
|
||||
.get(&handle.value())
|
||||
.map_or(0.0, |depth| depth[0]);
|
||||
|
||||
let mut models = Vec::with_capacity(2);
|
||||
if let EntityType::Hatch(hatch) = entity {
|
||||
if let Some(background) = crate::entities::hatch::background_color(hatch) {
|
||||
let mut backdrop = model.clone();
|
||||
backdrop.pattern =
|
||||
crate::scene::model::hatch_model::HatchPattern::Solid;
|
||||
backdrop.color = match background {
|
||||
acadrust::types::Color::ByLayer => {
|
||||
crate::scene::view::render::layer_render_style(
|
||||
&self.document,
|
||||
&hatch.common.layer,
|
||||
)
|
||||
.color
|
||||
}
|
||||
acadrust::types::Color::ByBlock => self.render_style(entity).0,
|
||||
other => crate::scene::convert::tess_util::aci_to_rgba(&other),
|
||||
};
|
||||
backdrop.name = "SOLID".into();
|
||||
models.push(backdrop);
|
||||
}
|
||||
}
|
||||
models.push(model);
|
||||
self.preview_hatches = std::sync::Arc::new(models);
|
||||
}
|
||||
|
||||
pub fn set_preview_text(&mut self, verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>) {
|
||||
// Overlay glyphs — same reasoning as `set_preview_wires`: no geometry
|
||||
// bump. Uploaded to a dedicated per-frame text buffer in `prepare`.
|
||||
@@ -25,6 +77,9 @@ impl Scene {
|
||||
// flips the wire content id back to the base tessellation id, which
|
||||
// re-uploads the base wires (without the preview) on the next frame.
|
||||
self.preview_wires = vec![];
|
||||
if !self.preview_hatches.is_empty() {
|
||||
self.preview_hatches = std::sync::Arc::new(Vec::new());
|
||||
}
|
||||
self.preview_text = vec![];
|
||||
self.interim_wire = None;
|
||||
// Drop any point-picked window marquee (STRETCH) so it doesn't linger
|
||||
|
||||
@@ -52,6 +52,9 @@ pub struct ViewportData {
|
||||
/// the main `wires` buffer so a drag re-uploads only this small set each
|
||||
/// frame, never the resident base buffer. Drawn on top in the wire pass.
|
||||
pub(in crate::scene) preview_wires: Arc<Vec<WireModel>>,
|
||||
/// One/few live hatch models for grip editing. Uploaded through a separate
|
||||
/// tiny GPU batch so the resident hatch buffer remains untouched.
|
||||
pub(in crate::scene) preview_hatches: Arc<Vec<HatchModel>>,
|
||||
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
||||
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
||||
pub(in crate::scene) face3d_wires: Arc<Vec<WireModel>>,
|
||||
@@ -268,6 +271,7 @@ impl shader::Primitive for Primitive {
|
||||
inner.cached_mesh_content_id = u64::MAX;
|
||||
inner.cached_face3d_key = (u64::MAX, false);
|
||||
inner.cached_hatch_source = None;
|
||||
inner.cached_preview_hatch_source = None;
|
||||
inner.cached_wipeout_source = None;
|
||||
inner.cached_image_source = None;
|
||||
inner.cached_text_source = None;
|
||||
@@ -371,6 +375,23 @@ impl shader::Primitive for Primitive {
|
||||
);
|
||||
inner.cached_hatch_source = Some(Arc::clone(&vp.hatches));
|
||||
}
|
||||
let preview_hatch_changed = inner
|
||||
.cached_preview_hatch_source
|
||||
.as_ref()
|
||||
.map_or(true, |source| !Arc::ptr_eq(source, &vp.preview_hatches));
|
||||
if preview_hatch_changed || fill_changed {
|
||||
inner.upload_preview_hatches(
|
||||
device,
|
||||
queue,
|
||||
if fill_mode {
|
||||
&vp.preview_hatches[..]
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
);
|
||||
inner.cached_preview_hatch_source =
|
||||
Some(Arc::clone(&vp.preview_hatches));
|
||||
}
|
||||
if wipeout_changed || fill_changed {
|
||||
inner.upload_wipeouts(
|
||||
device,
|
||||
@@ -1100,6 +1121,56 @@ fn render_signature(vp: &ViewportData, clip_w: u32, clip_h: u32) -> u64 {
|
||||
p[2].to_bits().hash(&mut h);
|
||||
}
|
||||
}
|
||||
// Live hatch preview. Pattern-origin grip drags keep the boundary fixed and
|
||||
// move only the family anchors, so hash both geometry and pattern data.
|
||||
vp.preview_hatches.len().hash(&mut h);
|
||||
for model in vp.preview_hatches.iter() {
|
||||
model.world_origin[0].to_bits().hash(&mut h);
|
||||
model.world_origin[1].to_bits().hash(&mut h);
|
||||
model.boundary.len().hash(&mut h);
|
||||
for point in model.boundary.iter() {
|
||||
point[0].to_bits().hash(&mut h);
|
||||
point[1].to_bits().hash(&mut h);
|
||||
}
|
||||
for component in model.color {
|
||||
component.to_bits().hash(&mut h);
|
||||
}
|
||||
model.angle_offset.to_bits().hash(&mut h);
|
||||
model.scale.to_bits().hash(&mut h);
|
||||
match &model.pattern {
|
||||
crate::scene::model::hatch_model::HatchPattern::Solid => {
|
||||
0_u8.hash(&mut h);
|
||||
}
|
||||
crate::scene::model::hatch_model::HatchPattern::Pattern(families) => {
|
||||
1_u8.hash(&mut h);
|
||||
families.len().hash(&mut h);
|
||||
for family in families {
|
||||
family.angle_deg.to_bits().hash(&mut h);
|
||||
family.x0.to_bits().hash(&mut h);
|
||||
family.y0.to_bits().hash(&mut h);
|
||||
family.dx.to_bits().hash(&mut h);
|
||||
family.dy.to_bits().hash(&mut h);
|
||||
for dash in &family.dashes {
|
||||
dash.to_bits().hash(&mut h);
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::scene::model::hatch_model::HatchPattern::Gradient {
|
||||
angle_deg,
|
||||
color2,
|
||||
kind,
|
||||
invert,
|
||||
} => {
|
||||
2_u8.hash(&mut h);
|
||||
angle_deg.to_bits().hash(&mut h);
|
||||
for component in color2 {
|
||||
component.to_bits().hash(&mut h);
|
||||
}
|
||||
kind.shader_kind().hash(&mut h);
|
||||
invert.hash(&mut h);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Grip-drag / command-preview glyph quads (issue #316). A pure-text slide
|
||||
// leaves `preview_wires` empty and moves ONLY these, so a signature that
|
||||
// ignored them would let the scene-render cache freeze the dragged text at
|
||||
@@ -2015,6 +2086,11 @@ impl Scene {
|
||||
v.extend(self.preview_wires.iter().cloned());
|
||||
Arc::new(v)
|
||||
};
|
||||
let preview_hatches = if show_live_overlay {
|
||||
Arc::clone(&self.preview_hatches)
|
||||
} else {
|
||||
Arc::new(Vec::new())
|
||||
};
|
||||
|
||||
// Build the camera at the *full* viewport's aspect so the ortho
|
||||
// frustum matches what the viewport entity stores, then post-
|
||||
@@ -2198,6 +2274,7 @@ impl Scene {
|
||||
wires: Arc::downgrade(&all_wires),
|
||||
clip_boundary_ndc,
|
||||
preview_wires,
|
||||
preview_hatches,
|
||||
face3d_wires,
|
||||
text_verts,
|
||||
preview_text_verts,
|
||||
|
||||
Reference in new issue
Block a user