forked from 1b-agent/opencadstudio-upstream
fix(hatch): pick points stay fast in large drawings, and see off layers
Each picked region matched every edge of its rings against every boundary source of the drawing, twice: with 65,000 sources a click took over 30 seconds. Sources are now narrowed to those reaching the rings' box first, once per hatch and once per ring: the same click takes 9 ms. Objects on layers that are turned off (not frozen) now bound a hatch, as in the reference. Drawings switch off the layers that close the regions to be hatched; without them a click between columns found the outer frame instead. (#1813) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d67fd101b4
commit
d2995ecee1
3 files changed
+70
-7
No files matched your search
@@ -350,12 +350,13 @@ impl HatchCommand {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|depth| depth == 0)
|
.map(|depth| depth == 0)
|
||||||
.collect();
|
.collect();
|
||||||
|
let near = crate::scene::sources_near_rings(&rings, &self.boundary_sources, 1.0e-6);
|
||||||
let mut boundary_sources: Vec<Vec<Handle>> = rings
|
let mut boundary_sources: Vec<Vec<Handle>> = rings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ring| crate::scene::ring_source_handles(ring, &self.boundary_sources))
|
.map(|ring| crate::scene::ring_source_handles(ring, &near))
|
||||||
.collect();
|
.collect();
|
||||||
let mut boundary_paths =
|
let mut boundary_paths = crate::scene::exact_hatch_paths(&rings, &exterior, &near, 1.0e-6);
|
||||||
crate::scene::exact_hatch_paths(&rings, &exterior, &self.boundary_sources, 1.0e-6);
|
|
||||||
if !self.associative {
|
if !self.associative {
|
||||||
for handles in &mut boundary_sources {
|
for handles in &mut boundary_sources {
|
||||||
handles.clear();
|
handles.clear();
|
||||||
@@ -983,12 +984,12 @@ impl GradientCommand {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|depth| depth == 0)
|
.map(|depth| depth == 0)
|
||||||
.collect();
|
.collect();
|
||||||
|
let near = crate::scene::sources_near_rings(&rings, &self.boundary_sources, 1.0e-6);
|
||||||
let boundary_sources = rings
|
let boundary_sources = rings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ring| crate::scene::ring_source_handles(ring, &self.boundary_sources))
|
.map(|ring| crate::scene::ring_source_handles(ring, &near))
|
||||||
.collect();
|
.collect();
|
||||||
let boundary_paths =
|
let boundary_paths = crate::scene::exact_hatch_paths(&rings, &exterior, &near, 1.0e-6);
|
||||||
crate::scene::exact_hatch_paths(&rings, &exterior, &self.boundary_sources, 1.0e-6);
|
|
||||||
HatchModel {
|
HatchModel {
|
||||||
pattern_origin: None,
|
pattern_origin: None,
|
||||||
render_instance: None,
|
render_instance: None,
|
||||||
|
|||||||
@@ -211,10 +211,41 @@ fn matching_face(faces: &[Vec<[f64; 2]>], seed: Option<[f64; 2]>) -> Option<&Vec
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sources with a segment reaching the box around `rings`: the only ones
|
||||||
|
/// their edges can lie on. Matching every edge against every source of a
|
||||||
|
/// large drawing took half a minute per picked hatch region. (#1813)
|
||||||
|
pub(crate) fn sources_near_rings(
|
||||||
|
rings: &[Vec<[f64; 2]>],
|
||||||
|
sources: &rustc_hash::FxHashMap<codec::Handle, BoundarySource>,
|
||||||
|
pad: f64,
|
||||||
|
) -> rustc_hash::FxHashMap<codec::Handle, BoundarySource> {
|
||||||
|
let mut lo = [f64::INFINITY; 2];
|
||||||
|
let mut hi = [f64::NEG_INFINITY; 2];
|
||||||
|
for point in rings.iter().flatten() {
|
||||||
|
for axis in 0..2 {
|
||||||
|
lo[axis] = lo[axis].min(point[axis] - pad);
|
||||||
|
hi[axis] = hi[axis].max(point[axis] + pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let reaches = |line: &Line| {
|
||||||
|
(0..2).all(|axis| {
|
||||||
|
line.start[axis].min(line.end[axis]) <= hi[axis]
|
||||||
|
&& line.start[axis].max(line.end[axis]) >= lo[axis]
|
||||||
|
})
|
||||||
|
};
|
||||||
|
sources
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, source)| source.segments.iter().any(reaches))
|
||||||
|
.map(|(handle, source)| (*handle, source.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn ring_source_handles(
|
pub(crate) fn ring_source_handles(
|
||||||
ring: &[[f64; 2]],
|
ring: &[[f64; 2]],
|
||||||
sources: &rustc_hash::FxHashMap<codec::Handle, BoundarySource>,
|
sources: &rustc_hash::FxHashMap<codec::Handle, BoundarySource>,
|
||||||
) -> Vec<codec::Handle> {
|
) -> Vec<codec::Handle> {
|
||||||
|
let near = sources_near_rings(&[ring.to_vec()], sources, WELD_TOLERANCE);
|
||||||
|
let sources = &near;
|
||||||
let mut handles = rustc_hash::FxHashSet::default();
|
let mut handles = rustc_hash::FxHashSet::default();
|
||||||
for (&start, &end) in ring
|
for (&start, &end) in ring
|
||||||
.iter()
|
.iter()
|
||||||
@@ -411,6 +442,8 @@ pub(crate) fn exact_hatch_paths(
|
|||||||
) -> Vec<codec::entities::BoundaryPath> {
|
) -> Vec<codec::entities::BoundaryPath> {
|
||||||
use codec::entities::{BoundaryPath, BoundaryPathFlags};
|
use codec::entities::{BoundaryPath, BoundaryPathFlags};
|
||||||
|
|
||||||
|
let near = sources_near_rings(rings, sources, tolerance.max(WELD_TOLERANCE));
|
||||||
|
let sources = &near;
|
||||||
rings
|
rings
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -568,6 +601,8 @@ fn refined_boundary_ring(
|
|||||||
if count < 3 {
|
if count < 3 {
|
||||||
return (ring.to_vec(), Vec::new());
|
return (ring.to_vec(), Vec::new());
|
||||||
}
|
}
|
||||||
|
let near = sources_near_rings(&[ring.to_vec()], sources, tolerance.max(WELD_TOLERANCE));
|
||||||
|
let sources = &near;
|
||||||
let edge_curves: Vec<_> = (0..count)
|
let edge_curves: Vec<_> = (0..count)
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
edge_curve(
|
edge_curve(
|
||||||
@@ -1022,6 +1057,32 @@ impl Scene {
|
|||||||
.segments
|
.segments
|
||||||
.extend(segments);
|
.extend(segments);
|
||||||
}
|
}
|
||||||
|
// Objects on a layer that is turned off (not frozen) still bound a
|
||||||
|
// hatch, as in the reference: drawings switch off the layers that
|
||||||
|
// close the regions to be hatched. They draw no wire, so they come
|
||||||
|
// from the document. (#1813)
|
||||||
|
let space = if self.active_viewport.is_some() || self.current_layout == "Model" {
|
||||||
|
self.content_render_block_handle()
|
||||||
|
} else {
|
||||||
|
self.current_layout_block_handle()
|
||||||
|
};
|
||||||
|
for entity in self.document.entities() {
|
||||||
|
let common = entity.common();
|
||||||
|
if common.owner_handle != space || sources.contains_key(&common.handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let hidden = self
|
||||||
|
.document
|
||||||
|
.layers
|
||||||
|
.get(&common.layer)
|
||||||
|
.is_some_and(|layer| layer.is_off() && !layer.is_frozen());
|
||||||
|
if !hidden {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(source) = entity_boundary_source_on_plane(entity, plane, tolerance, None) {
|
||||||
|
sources.insert(common.handle, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Finite extents of the wire geometry, so rays and infinite
|
// Finite extents of the wire geometry, so rays and infinite
|
||||||
// construction lines clip to something real instead of tessellating
|
// construction lines clip to something real instead of tessellating
|
||||||
// to a unit stub (see `live_segments_for_curves`).
|
// to a unit stub (see `live_segments_for_curves`).
|
||||||
|
|||||||
+2
-1
@@ -80,7 +80,8 @@ mod parametric_solve;
|
|||||||
pub(crate) use boundary::{
|
pub(crate) use boundary::{
|
||||||
boundary_entities, boundary_entities_from_sources, boundary_faces, boundary_polyline_entities,
|
boundary_entities, boundary_entities_from_sources, boundary_faces, boundary_polyline_entities,
|
||||||
exact_hatch_paths, hatch_boundary_rings, hatch_path_directions, hatch_path_ring,
|
exact_hatch_paths, hatch_boundary_rings, hatch_path_directions, hatch_path_ring,
|
||||||
remap_hatch_associations, ring_source_handles, separated_hatch_path_groups, BoundarySource,
|
remap_hatch_associations, ring_source_handles, separated_hatch_path_groups, sources_near_rings,
|
||||||
|
BoundarySource,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parallel tessellation free functions live in `convert::tess` (alongside the
|
// Parallel tessellation free functions live in `convert::tess` (alongside the
|
||||||
|
|||||||
Reference in new issue
Block a user