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]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.apply_grip(grip.handle, grip.grip_id, apply);
|
.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].dirty = true;
|
||||||
self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped;
|
self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped;
|
||||||
let apply_ms = apply_started.elapsed().as_secs_f64() * 1000.0;
|
let apply_ms = apply_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
|
|||||||
@@ -1536,6 +1536,10 @@ pub struct Scene {
|
|||||||
pub selection_filter: HashSet<String>,
|
pub selection_filter: HashSet<String>,
|
||||||
/// In-progress preview wires while a command is active (rubber-band + object ghosts).
|
/// In-progress preview wires while a command is active (rubber-band + object ghosts).
|
||||||
pub preview_wires: Vec<WireModel>,
|
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
|
/// 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
|
/// 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).
|
/// dragged text stays visible while it's hidden from the base set (#316).
|
||||||
@@ -1911,6 +1915,7 @@ impl Scene {
|
|||||||
transparency_display: true,
|
transparency_display: true,
|
||||||
selection_filter: HashSet::default(),
|
selection_filter: HashSet::default(),
|
||||||
preview_wires: vec![],
|
preview_wires: vec![],
|
||||||
|
preview_hatches: Arc::new(Vec::new()),
|
||||||
preview_text: vec![],
|
preview_text: vec![],
|
||||||
interim_wire: None,
|
interim_wire: None,
|
||||||
camera_generation: 0,
|
camera_generation: 0,
|
||||||
|
|||||||
@@ -257,6 +257,10 @@ pub struct Pipeline {
|
|||||||
gpu_hatch: Option<hatch_gpu::HatchGpu>,
|
gpu_hatch: Option<hatch_gpu::HatchGpu>,
|
||||||
/// Compatibility hatch fills — one texture-backed GPU object per hatch.
|
/// Compatibility hatch fills — one texture-backed GPU object per hatch.
|
||||||
gpu_hatches_compat: Vec<hatch_web_gpu::HatchWebGpu>,
|
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
|
/// Wipeout masks — solid fills rendered after wires in a separate pass via
|
||||||
/// the legacy per-primitive `WipeoutGpu` renderer.
|
/// the legacy per-primitive `WipeoutGpu` renderer.
|
||||||
gpu_wipeouts: Vec<WipeoutGpu>,
|
gpu_wipeouts: Vec<WipeoutGpu>,
|
||||||
@@ -293,6 +297,7 @@ pub struct Pipeline {
|
|||||||
/// Arc makes pointer identity ABA-safe: an unchanged category reuses the
|
/// Arc makes pointer identity ABA-safe: an unchanged category reuses the
|
||||||
/// same Arc even when an unrelated entity advances `geometry_epoch`.
|
/// same Arc even when an unrelated entity advances `geometry_epoch`.
|
||||||
pub cached_hatch_source: Option<std::sync::Arc<Vec<HatchModel>>>,
|
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_wipeout_source: Option<std::sync::Arc<Vec<HatchModel>>>,
|
||||||
pub cached_image_source: Option<std::sync::Arc<Vec<ImageModel>>>,
|
pub cached_image_source: Option<std::sync::Arc<Vec<ImageModel>>>,
|
||||||
pub cached_text_source: Option<std::sync::Arc<Vec<text_gpu::TextVertex>>>,
|
pub cached_text_source: Option<std::sync::Arc<Vec<text_gpu::TextVertex>>>,
|
||||||
@@ -1827,6 +1832,8 @@ impl Pipeline {
|
|||||||
gpu_preview_wires: vec![],
|
gpu_preview_wires: vec![],
|
||||||
gpu_hatch: None,
|
gpu_hatch: None,
|
||||||
gpu_hatches_compat: vec![],
|
gpu_hatches_compat: vec![],
|
||||||
|
gpu_preview_hatch: None,
|
||||||
|
gpu_preview_hatches_compat: vec![],
|
||||||
gpu_wipeouts: vec![],
|
gpu_wipeouts: vec![],
|
||||||
wipeout_skip_flags: vec![],
|
wipeout_skip_flags: vec![],
|
||||||
gpu_images: vec![],
|
gpu_images: vec![],
|
||||||
@@ -1842,6 +1849,7 @@ impl Pipeline {
|
|||||||
gpu_face3d_edges: vec![],
|
gpu_face3d_edges: vec![],
|
||||||
viewcube,
|
viewcube,
|
||||||
cached_hatch_source: None,
|
cached_hatch_source: None,
|
||||||
|
cached_preview_hatch_source: None,
|
||||||
cached_wipeout_source: None,
|
cached_wipeout_source: None,
|
||||||
cached_image_source: None,
|
cached_image_source: None,
|
||||||
cached_text_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]) {
|
pub fn upload_wipeouts(&mut self, device: &wgpu::Device, wipeouts: &[HatchModel]) {
|
||||||
self.gpu_wipeouts = wipeouts
|
self.gpu_wipeouts = wipeouts
|
||||||
.iter()
|
.iter()
|
||||||
@@ -3063,6 +3100,18 @@ impl Pipeline {
|
|||||||
pass.draw(0..batch.vertex_count, 0..1);
|
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
|
// Storage-free compatibility path. Draw before wires so outlines
|
||||||
// remain on top, matching the batched path.
|
// remain on top, matching the batched path.
|
||||||
@@ -3076,6 +3125,11 @@ impl Pipeline {
|
|||||||
pass.set_vertex_buffer(0, hatch.vertex_buffer.slice(..));
|
pass.set_vertex_buffer(0, hatch.vertex_buffer.slice(..));
|
||||||
pass.draw(0..6, 0..1);
|
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;
|
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>) {
|
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
|
// Overlay glyphs — same reasoning as `set_preview_wires`: no geometry
|
||||||
// bump. Uploaded to a dedicated per-frame text buffer in `prepare`.
|
// 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
|
// flips the wire content id back to the base tessellation id, which
|
||||||
// re-uploads the base wires (without the preview) on the next frame.
|
// re-uploads the base wires (without the preview) on the next frame.
|
||||||
self.preview_wires = vec![];
|
self.preview_wires = vec![];
|
||||||
|
if !self.preview_hatches.is_empty() {
|
||||||
|
self.preview_hatches = std::sync::Arc::new(Vec::new());
|
||||||
|
}
|
||||||
self.preview_text = vec![];
|
self.preview_text = vec![];
|
||||||
self.interim_wire = None;
|
self.interim_wire = None;
|
||||||
// Drop any point-picked window marquee (STRETCH) so it doesn't linger
|
// 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
|
/// 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.
|
/// frame, never the resident base buffer. Drawn on top in the wire pass.
|
||||||
pub(in crate::scene) preview_wires: Arc<Vec<WireModel>>,
|
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
|
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
||||||
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
||||||
pub(in crate::scene) face3d_wires: Arc<Vec<WireModel>>,
|
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_mesh_content_id = u64::MAX;
|
||||||
inner.cached_face3d_key = (u64::MAX, false);
|
inner.cached_face3d_key = (u64::MAX, false);
|
||||||
inner.cached_hatch_source = None;
|
inner.cached_hatch_source = None;
|
||||||
|
inner.cached_preview_hatch_source = None;
|
||||||
inner.cached_wipeout_source = None;
|
inner.cached_wipeout_source = None;
|
||||||
inner.cached_image_source = None;
|
inner.cached_image_source = None;
|
||||||
inner.cached_text_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));
|
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 {
|
if wipeout_changed || fill_changed {
|
||||||
inner.upload_wipeouts(
|
inner.upload_wipeouts(
|
||||||
device,
|
device,
|
||||||
@@ -1100,6 +1121,56 @@ fn render_signature(vp: &ViewportData, clip_w: u32, clip_h: u32) -> u64 {
|
|||||||
p[2].to_bits().hash(&mut h);
|
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
|
// Grip-drag / command-preview glyph quads (issue #316). A pure-text slide
|
||||||
// leaves `preview_wires` empty and moves ONLY these, so a signature that
|
// 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
|
// 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());
|
v.extend(self.preview_wires.iter().cloned());
|
||||||
Arc::new(v)
|
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
|
// Build the camera at the *full* viewport's aspect so the ortho
|
||||||
// frustum matches what the viewport entity stores, then post-
|
// frustum matches what the viewport entity stores, then post-
|
||||||
@@ -2198,6 +2274,7 @@ impl Scene {
|
|||||||
wires: Arc::downgrade(&all_wires),
|
wires: Arc::downgrade(&all_wires),
|
||||||
clip_boundary_ndc,
|
clip_boundary_ndc,
|
||||||
preview_wires,
|
preview_wires,
|
||||||
|
preview_hatches,
|
||||||
face3d_wires,
|
face3d_wires,
|
||||||
text_verts,
|
text_verts,
|
||||||
preview_text_verts,
|
preview_text_verts,
|
||||||
|
|||||||
Reference in new issue
Block a user