forked from 1b-agent/opencadstudio-upstream
DWF/DGN underlays: draw content as vector wires
A DWF sheet or DGN model is drawn as world-space wires (one line wire per colour and width, one fill wire per colour) instead of a raster rescaled to the view, so it stays sharp while zooming instead of showing pixels until the raster is made again. Colours go through the colour table mapping, contrast, fade and monochrome; clip boundaries clip the wires. The wires are drawn and plotted but neither picked nor snapped; the underlay's own snap geometry still serves object snaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be023142de
commit
507a6ab43c
8 files changed
+310
-100
No files matched your search
@@ -1321,6 +1321,10 @@ fn tessellate_entity_inner(
|
||||
set_wire_aabb(&mut wire, WireModel::UNBOUNDED_AABB);
|
||||
bases.push(wire);
|
||||
}
|
||||
// DWF / DGN content: vector wires, sharp at every zoom.
|
||||
if let Some(def) = crate::entities::underlay::definition(underlay, document) {
|
||||
bases.extend(crate::scene::model::underlay_vector::display_wires(underlay, def, &h.value().to_string(), bg_color));
|
||||
}
|
||||
}
|
||||
|
||||
// A hidden mask frame remains selectable and appears while selected, but
|
||||
|
||||
@@ -99,6 +99,59 @@ pub struct ImageModel {
|
||||
pub use_alpha: bool,
|
||||
}
|
||||
|
||||
/// The colour adjustments an underlay's content is drawn with over
|
||||
/// the background: contrast, monochrome, the background adjustment and (DGN)
|
||||
/// the fade.
|
||||
pub(crate) fn underlay_adjust(
|
||||
u: &codec::entities::Underlay,
|
||||
def: &codec::entities::UnderlayDefinition,
|
||||
background: [f32; 4],
|
||||
) -> super::pdf_raster::PageAdjust {
|
||||
use codec::entities::{UnderlayDisplayFlags, UnderlayType};
|
||||
use super::pdf_raster::PageAdjust;
|
||||
let page = crate::entities::underlay::page_of(def);
|
||||
// Dark means an HSL lightness under one half: pure blue counts as
|
||||
// light, (0, 128, 0) as dark.
|
||||
let bg_max = background[0].max(background[1]).max(background[2]);
|
||||
let bg_min = background[0].min(background[1]).min(background[2]);
|
||||
let bg_lum = (bg_max + bg_min) / 2.0;
|
||||
let dgn = def.underlay_type == UnderlayType::Dgn;
|
||||
// A DGN model is faded into the background before its contrast is
|
||||
// applied (the reference's order), so it is drawn opaque.
|
||||
let bg_rgb = [0, 1, 2].map(|i| (background[i].clamp(0.0, 1.0) * 255.0).round() as u8);
|
||||
let fade_before_contrast = (dgn && u.fade > 0).then_some((bg_rgb, u.fade.min(100)));
|
||||
PageAdjust {
|
||||
contrast: u.contrast.min(100),
|
||||
// Measured: PDF 0.5; DWF 0.243, or 0.73 once its colours are
|
||||
// turned over for a dark background; a DGN model's from its
|
||||
// own colours, over any background.
|
||||
contrast_pivot: match def.underlay_type {
|
||||
UnderlayType::Pdf => 500,
|
||||
UnderlayType::Dwf
|
||||
if u.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND) && bg_lum < 0.5 =>
|
||||
{
|
||||
730
|
||||
}
|
||||
UnderlayType::Dwf => 243,
|
||||
UnderlayType::Dgn => super::underlay_vector::dgn_contrast_pivot(
|
||||
&def.file_path,
|
||||
page,
|
||||
&super::pdf_layers::hidden_layers(u),
|
||||
super::underlay_vector::Backdrop::of([background[0], background[1], background[2]]),
|
||||
fade_before_contrast,
|
||||
)
|
||||
.unwrap_or(404),
|
||||
},
|
||||
monochrome: u.flags.contains(UnderlayDisplayFlags::MONOCHROME),
|
||||
// A DGN model's colours follow the background through its
|
||||
// colour table, whatever this setting.
|
||||
adjust_for_background: u.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND)
|
||||
&& !dgn,
|
||||
dark_background: bg_lum < 0.5,
|
||||
fade_before_contrast,
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageModel {
|
||||
/// Build an ImageModel from a DXF RasterImage entity.
|
||||
/// Returns `None` if the image file cannot be opened or decoded.
|
||||
@@ -197,7 +250,7 @@ impl ImageModel {
|
||||
world_per_pixel: Option<f64>,
|
||||
) -> Option<Self> {
|
||||
use codec::entities::{UnderlayDisplayFlags, UnderlayType};
|
||||
use super::pdf_raster::{self, PageAdjust};
|
||||
use super::pdf_raster;
|
||||
if !u.flags.contains(UnderlayDisplayFlags::ON) {
|
||||
return None;
|
||||
}
|
||||
@@ -229,74 +282,11 @@ impl ImageModel {
|
||||
};
|
||||
(source, raster)
|
||||
}
|
||||
kind => {
|
||||
let hidden = super::pdf_layers::hidden_layers(u);
|
||||
let backdrop = super::underlay_vector::Backdrop::of([background[0], background[1], background[2]]);
|
||||
// The adjusted-pixel memo keys on the source: one per set of
|
||||
// hidden layers and backdrop.
|
||||
let source = format!(
|
||||
"{}#{}~{}{}",
|
||||
def.file_path,
|
||||
hidden.join("|"),
|
||||
backdrop.max_channel,
|
||||
backdrop.light
|
||||
);
|
||||
let raster = super::underlay_vector::display_raster(
|
||||
kind,
|
||||
&def.file_path,
|
||||
page,
|
||||
&hidden,
|
||||
screen_side.unwrap_or(super::underlay_vector::RASTER_SIDE),
|
||||
backdrop,
|
||||
)?;
|
||||
(source, raster)
|
||||
}
|
||||
// DWF sheets and DGN models are drawn as vector wires
|
||||
// (`underlay_vector::display_wires`).
|
||||
_ => return None,
|
||||
};
|
||||
// Dark means an HSL lightness under one half: pure blue counts as
|
||||
// light, (0, 128, 0) as dark.
|
||||
let bg_max = background[0].max(background[1]).max(background[2]);
|
||||
let bg_min = background[0].min(background[1]).min(background[2]);
|
||||
let bg_lum = (bg_max + bg_min) / 2.0;
|
||||
let dgn = def.underlay_type == UnderlayType::Dgn;
|
||||
// A DGN model is faded into the background before its contrast is
|
||||
// applied (the reference's order), so it is drawn opaque.
|
||||
let bg_rgb = [0, 1, 2].map(|i| (background[i].clamp(0.0, 1.0) * 255.0).round() as u8);
|
||||
let fade_before_contrast = (dgn && u.fade > 0).then_some((bg_rgb, u.fade.min(100)));
|
||||
let pixels = pdf_raster::adjusted_pixels(
|
||||
&source,
|
||||
page,
|
||||
&raster,
|
||||
PageAdjust {
|
||||
contrast: u.contrast.min(100),
|
||||
// Measured: PDF 0.5; DWF 0.243, or 0.73 once its colours are
|
||||
// turned over for a dark background; a DGN model's from its
|
||||
// own colours, over any background.
|
||||
contrast_pivot: match def.underlay_type {
|
||||
UnderlayType::Pdf => 500,
|
||||
UnderlayType::Dwf
|
||||
if u.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND) && bg_lum < 0.5 =>
|
||||
{
|
||||
730
|
||||
}
|
||||
UnderlayType::Dwf => 243,
|
||||
UnderlayType::Dgn => super::underlay_vector::dgn_contrast_pivot(
|
||||
&def.file_path,
|
||||
page,
|
||||
&super::pdf_layers::hidden_layers(u),
|
||||
super::underlay_vector::Backdrop::of([background[0], background[1], background[2]]),
|
||||
fade_before_contrast,
|
||||
)
|
||||
.unwrap_or(404),
|
||||
},
|
||||
monochrome: u.flags.contains(UnderlayDisplayFlags::MONOCHROME),
|
||||
// A DGN model's colours follow the background through its
|
||||
// colour table (the raster above), whatever this setting.
|
||||
adjust_for_background: u.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND)
|
||||
&& !dgn,
|
||||
dark_background: bg_lum < 0.5,
|
||||
fade_before_contrast,
|
||||
},
|
||||
);
|
||||
let pixels = pdf_raster::adjusted_pixels(&source, page, &raster, underlay_adjust(u, def, background));
|
||||
|
||||
// The page rectangle in drawing units (1 unit per PDF inch, or per
|
||||
// DWF/DGN sheet unit), entity scale applied, from its lower-left
|
||||
@@ -347,7 +337,7 @@ impl ImageModel {
|
||||
pixels,
|
||||
width: raster.width,
|
||||
height: raster.height,
|
||||
opacity: if dgn { 1.0 } else { 1.0 - u.fade.min(100) as f32 / 100.0 },
|
||||
opacity: 1.0 - u.fade.min(100) as f32 / 100.0,
|
||||
corners,
|
||||
corners_low,
|
||||
draw_depth: 0.0,
|
||||
|
||||
@@ -238,6 +238,39 @@ fn adjusted_cache() -> &'static Mutex<HashMap<AdjustKey, Arc<Vec<u8>>>> {
|
||||
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
/// One colour (0..=1 channels) with the underlay's contrast / monochrome /
|
||||
/// background adjustments applied.
|
||||
pub fn adjust_rgb(rgb: [f32; 3], adjust: &PageAdjust) -> [f32; 3] {
|
||||
let mut rgb = rgb;
|
||||
if adjust.monochrome {
|
||||
let l = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
|
||||
rgb = [l; 3];
|
||||
}
|
||||
if adjust.adjust_for_background && adjust.dark_background {
|
||||
// On a dark background every colour's lightness is turned over
|
||||
// (plus 20/255, capped at full), hue and saturation kept: black
|
||||
// becomes white, dark red light pink. On a light background the
|
||||
// colours stay as drawn.
|
||||
let (h, s, l) = rgb_to_hsl(rgb);
|
||||
rgb = hsl_to_rgb(h, s, (1.0 - l + 20.0 / 255.0).min(1.0));
|
||||
}
|
||||
if let Some((bg, fade)) = adjust.fade_before_contrast {
|
||||
let f = fade.min(100) as f32 / 100.0;
|
||||
for (c, b) in rgb.iter_mut().zip(bg) {
|
||||
*c += (b as f32 / 255.0 - *c) * f;
|
||||
}
|
||||
}
|
||||
if adjust.contrast < 100 {
|
||||
// Lightness is pulled towards the pivot, hue and saturation kept:
|
||||
// contrast 0 leaves every colour at the pivot's lightness.
|
||||
let (h, s, l) = rgb_to_hsl(rgb);
|
||||
let k = adjust.contrast as f32 / 100.0;
|
||||
let pivot = adjust.contrast_pivot as f32 / 1000.0;
|
||||
rgb = hsl_to_rgb(h, s, pivot + (l - pivot) * k);
|
||||
}
|
||||
rgb
|
||||
}
|
||||
|
||||
/// The display raster with the underlay's contrast / monochrome / background
|
||||
/// adjustments applied, memoised per source, page and adjustment.
|
||||
pub fn adjusted_pixels(path: &str, page: &str, raster: &PdfPage, adjust: PageAdjust) -> Arc<Vec<u8>> {
|
||||
@@ -260,34 +293,7 @@ pub fn adjusted_pixels(path: &str, page: &str, raster: &PdfPage, adjust: PageAdj
|
||||
if px[3] == 0 {
|
||||
continue;
|
||||
}
|
||||
let mut rgb = [px[0] as f32 / 255.0, px[1] as f32 / 255.0, px[2] as f32 / 255.0];
|
||||
if adjust.monochrome {
|
||||
let l = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
|
||||
rgb = [l; 3];
|
||||
}
|
||||
if adjust.adjust_for_background && adjust.dark_background {
|
||||
// On a dark background every colour's lightness is turned over
|
||||
// (plus 20/255, capped at full), hue and saturation kept: black
|
||||
// becomes white, dark red light pink. On a light background the
|
||||
// colours stay as drawn.
|
||||
let (h, s, l) = rgb_to_hsl(rgb);
|
||||
rgb = hsl_to_rgb(h, s, (1.0 - l + 20.0 / 255.0).min(1.0));
|
||||
}
|
||||
if let Some((bg, fade)) = adjust.fade_before_contrast {
|
||||
let f = fade.min(100) as f32 / 100.0;
|
||||
for (c, b) in rgb.iter_mut().zip(bg) {
|
||||
*c += (b as f32 / 255.0 - *c) * f;
|
||||
}
|
||||
}
|
||||
if adjust.contrast < 100 {
|
||||
// Lightness is pulled towards the pivot, hue and saturation kept:
|
||||
// contrast 0 leaves every colour at the pivot's lightness.
|
||||
let (h, s, l) = rgb_to_hsl(rgb);
|
||||
let k = adjust.contrast as f32 / 100.0;
|
||||
let pivot = adjust.contrast_pivot as f32 / 1000.0;
|
||||
rgb = hsl_to_rgb(h, s, pivot + (l - pivot) * k);
|
||||
}
|
||||
|
||||
let rgb = adjust_rgb([px[0] as f32 / 255.0, px[1] as f32 / 255.0, px[2] as f32 / 255.0], &adjust);
|
||||
for (dst, c) in px[..3].iter_mut().zip(rgb) {
|
||||
*dst = (c.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ mod dgn7;
|
||||
mod dgn8;
|
||||
mod dwf;
|
||||
mod model;
|
||||
mod wires;
|
||||
|
||||
pub(crate) use wires::display_wires;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
//! A DWF sheet or DGN model drawn as vector wires (lines and flat fills)
|
||||
//! in world space, so it stays sharp at every zoom as the reference draws
|
||||
//! it, instead of as a raster rescaled to the view.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codec::entities::{Underlay, UnderlayDefinition, UnderlayDisplayFlags, UnderlayType};
|
||||
|
||||
use super::{model, sheet_without, Backdrop, Segment, Sheet};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
/// Wires for the underlay's content, one per colour and kind (lines, fills),
|
||||
/// named after the underlay so they draw in its place. They are drawn and
|
||||
/// plotted but never picked or snapped (the frame selects the underlay; the
|
||||
/// underlay's own snap geometry serves object snaps).
|
||||
pub(crate) fn display_wires(
|
||||
u: &Underlay,
|
||||
def: &UnderlayDefinition,
|
||||
name: &str,
|
||||
background: [f32; 4],
|
||||
) -> Vec<WireModel> {
|
||||
if def.underlay_type == UnderlayType::Pdf || def.unloaded || !u.flags.contains(UnderlayDisplayFlags::ON) {
|
||||
return Vec::new();
|
||||
}
|
||||
let page = crate::entities::underlay::page_of(def);
|
||||
let hidden = crate::scene::model::pdf_layers::hidden_layers(u);
|
||||
let Some(sheet) = sheet_without(def.underlay_type, &def.file_path, page, &hidden) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let adjust = crate::scene::model::image_model::underlay_adjust(u, def, background);
|
||||
let backdrop = Backdrop::of([background[0], background[1], background[2]]);
|
||||
// A DGN model's fade is in its colours; a DWF sheet's blends it with
|
||||
// what is under it.
|
||||
let alpha = if def.underlay_type == UnderlayType::Dgn { 1.0 } else { 1.0 - u.fade.min(100) as f32 / 100.0 };
|
||||
let shown = |c: [u8; 3]| -> [f32; 4] {
|
||||
let c = if sheet.table_colors { model::dgn_table_color(c, backdrop.max_channel, backdrop.light) } else { c };
|
||||
let rgb = crate::scene::model::pdf_raster::adjust_rgb([c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0], &adjust);
|
||||
[rgb[0], rgb[1], rgb[2], alpha]
|
||||
};
|
||||
let world = |p: [f64; 2]| crate::entities::underlay::local_to_world(u, p);
|
||||
let tolerance = curve_tolerance(&sheet);
|
||||
let scale = u.x_scale.abs().max(u.y_scale.abs());
|
||||
|
||||
// Lines per (colour, width), fills per colour, each in first-drawn order.
|
||||
let key = |c: [f32; 4]| c.map(f32::to_bits);
|
||||
let mut lines: Vec<([f32; 4], f64, Vec<[f64; 3]>)> = Vec::new();
|
||||
let mut line_at: HashMap<([u32; 4], u64), usize> = HashMap::new();
|
||||
let mut fills: Vec<([f32; 4], Vec<[f64; 3]>)> = Vec::new();
|
||||
let mut fill_at: HashMap<[u32; 4], usize> = HashMap::new();
|
||||
for path in &sheet.paths {
|
||||
if let Some(c) = path.fill {
|
||||
let color = shown(c);
|
||||
let rings: Vec<Vec<[f64; 2]>> = path
|
||||
.subpaths
|
||||
.iter()
|
||||
.map(|sp| polyline(&sp.segments, tolerance))
|
||||
.filter(|r| r.len() >= 3)
|
||||
.collect();
|
||||
if !rings.is_empty() {
|
||||
let (points, triangles) = kernel::geom2d::triangulate_rings(&rings);
|
||||
let at = *fill_at.entry(key(color)).or_insert_with(|| {
|
||||
fills.push((color, Vec::new()));
|
||||
fills.len() - 1
|
||||
});
|
||||
for triangle in triangles {
|
||||
for index in triangle {
|
||||
if let Some(&p) = points.get(index) {
|
||||
fills[at].1.push(world(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((c, width)) = path.stroke {
|
||||
let color = shown(c);
|
||||
let at = *line_at.entry((key(color), width.to_bits())).or_insert_with(|| {
|
||||
lines.push((color, width, Vec::new()));
|
||||
lines.len() - 1
|
||||
});
|
||||
for sp in &path.subpaths {
|
||||
let mut pts = polyline(&sp.segments, tolerance);
|
||||
if sp.closed && pts.len() >= 2 && pts.first() != pts.last() {
|
||||
pts.push(pts[0]);
|
||||
}
|
||||
if pts.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
let run = &mut lines[at].2;
|
||||
if !run.is_empty() {
|
||||
run.push([f64::NAN; 3]);
|
||||
}
|
||||
run.extend(pts.into_iter().map(world));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut wires = Vec::new();
|
||||
for (color, tris) in fills {
|
||||
let (fp, fp_low) = crate::scene::convert::tessellate::points_to_ds(tris);
|
||||
let mut wire = content_wire(name, Vec::new(), color);
|
||||
wire.fill_tris = fp;
|
||||
wire.fill_tris_low = fp_low;
|
||||
wires.push(wire);
|
||||
}
|
||||
for (color, width, points) in lines {
|
||||
let (lp, lp_low) = crate::scene::convert::tessellate::points_to_ds(points);
|
||||
let mut wire = content_wire(name, lp, color);
|
||||
wire.points_low = lp_low;
|
||||
if width < 0.0 {
|
||||
// A width in pixels (DGN line weights are not shown).
|
||||
wire.set_fixed_screen_width((-width) as f32);
|
||||
} else if width > 0.0 {
|
||||
wire.world_width = (width * scale) as f32;
|
||||
} else {
|
||||
wire.set_fixed_screen_width(1.0);
|
||||
}
|
||||
wires.push(wire);
|
||||
}
|
||||
if crate::entities::underlay::is_clipped(u) {
|
||||
let clip: Vec<[f64; 2]> = crate::entities::underlay::clip_polygon_local(u)
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let w = world(p);
|
||||
[w[0], w[1]]
|
||||
})
|
||||
.collect();
|
||||
let ring = if u.clip_inverted {
|
||||
let [x0, y0, x1, y1] = sheet.rect;
|
||||
let corners = [[x0, y0], [x1, y0], [x1, y1], [x0, y1]].map(|p| {
|
||||
let w = world(p);
|
||||
[w[0], w[1]]
|
||||
});
|
||||
let lo = [corners.iter().map(|p| p[0]).fold(f64::MAX, f64::min), corners.iter().map(|p| p[1]).fold(f64::MAX, f64::min)];
|
||||
let hi = [corners.iter().map(|p| p[0]).fold(f64::MIN, f64::max), corners.iter().map(|p| p[1]).fold(f64::MIN, f64::max)];
|
||||
crate::scene::pick::xclip::inverted_ring(&clip, (lo, hi))
|
||||
} else {
|
||||
clip
|
||||
};
|
||||
crate::scene::pick::xclip::clip_wires(&mut wires, &ring);
|
||||
}
|
||||
for wire in &mut wires {
|
||||
crate::scene::convert::tess::set_wire_aabb(wire, WireModel::UNBOUNDED_AABB);
|
||||
}
|
||||
wires
|
||||
}
|
||||
|
||||
fn content_wire(name: &str, points: Vec<[f32; 3]>, color: [f32; 4]) -> WireModel {
|
||||
let mut wire = WireModel::solid(name.to_string(), points, color, false);
|
||||
wire.aci = 0;
|
||||
wire.bg_adapt = None;
|
||||
wire.display_visible = true;
|
||||
wire.plot_visible = true;
|
||||
// Drawn, but neither picked nor snapped (see `WireModel::snap_only`).
|
||||
wire.snap_only = true;
|
||||
wire
|
||||
}
|
||||
|
||||
/// How far a curve's polyline may stray from it: a small share of the
|
||||
/// sheet, so curves stay smooth when zoomed far in.
|
||||
fn curve_tolerance(sheet: &Sheet) -> f64 {
|
||||
let [x0, y0, x1, y1] = sheet.rect;
|
||||
((x1 - x0).hypot(y1 - y0) * 2e-5).max(1e-9)
|
||||
}
|
||||
|
||||
/// A subpath's points, curves divided within `tolerance`.
|
||||
fn polyline(segments: &[Segment], tolerance: f64) -> Vec<[f64; 2]> {
|
||||
let mut out: Vec<[f64; 2]> = Vec::new();
|
||||
for seg in segments {
|
||||
let start = seg.start();
|
||||
if out.last() != Some(&start) {
|
||||
out.push(start);
|
||||
}
|
||||
match seg {
|
||||
Segment::Line(_, b) => out.push(*b),
|
||||
Segment::Cubic(a, c1, c2, b) => {
|
||||
// The control polygon's length bounds the curve's; the
|
||||
// flattening error of n pieces falls as 1/n².
|
||||
let hull = dist(*a, *c1) + dist(*c1, *c2) + dist(*c2, *b);
|
||||
let n = ((hull / tolerance).sqrt() * 0.5).ceil().clamp(2.0, 256.0) as usize;
|
||||
for i in 1..=n {
|
||||
let t = i as f64 / n as f64;
|
||||
let u = 1.0 - t;
|
||||
let (k0, k1, k2, k3) = (u * u * u, 3.0 * u * u * t, 3.0 * u * t * t, t * t * t);
|
||||
out.push([
|
||||
k0 * a[0] + k1 * c1[0] + k2 * c2[0] + k3 * b[0],
|
||||
k0 * a[1] + k1 * c1[1] + k2 * c2[1] + k3 * b[1],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn dist(a: [f64; 2], b: [f64; 2]) -> f64 {
|
||||
(b[0] - a[0]).hypot(b[1] - a[1])
|
||||
}
|
||||
@@ -506,8 +506,9 @@ pub struct WireModel {
|
||||
pub depth_override: Option<f32>,
|
||||
/// Whether this wire is drawn in the normal viewport pass.
|
||||
pub display_visible: bool,
|
||||
/// Geometry that only object snaps use (the content of an underlay):
|
||||
/// never drawn, plotted or picked.
|
||||
/// Geometry that is never picked: the content of an underlay. Hidden
|
||||
/// (`display_visible` false) it is what object snaps find; drawn, it is
|
||||
/// display only and snaps skip it too ([`Self::is_display_only`]).
|
||||
pub snap_only: bool,
|
||||
/// Whether this wire is included in plotted output.
|
||||
pub plot_visible: bool,
|
||||
@@ -531,6 +532,11 @@ impl WireModel {
|
||||
pub const SELECTED: [f32; 4] = [0.15, 0.55, 1.00, 1.0];
|
||||
/// Rollover (hover) highlight — orange, distinct from the blue selection.
|
||||
pub const HOVER: [f32; 4] = [0.95, 0.55, 0.10, 1.0];
|
||||
|
||||
/// Drawn underlay content: shown and plotted, never picked or snapped.
|
||||
pub fn is_display_only(&self) -> bool {
|
||||
self.snap_only && self.display_visible
|
||||
}
|
||||
/// Sentinel AABB that never rejects any snap query.
|
||||
pub const UNBOUNDED_AABB: [f32; 4] = [
|
||||
f32::NEG_INFINITY,
|
||||
|
||||
@@ -673,6 +673,10 @@ impl WireIndexBatch {
|
||||
}
|
||||
|
||||
fn append_wire_index_entries(wire_idx: u32, wire: &WireModel, batch: &mut WireIndexBatch) {
|
||||
// Drawn underlay content: neither picked nor snapped.
|
||||
if wire.is_display_only() {
|
||||
return;
|
||||
}
|
||||
if let Some(aabb) = finite_wire_aabb3(wire) {
|
||||
batch.wire_entries.push(Entry3 {
|
||||
aabb,
|
||||
|
||||
+1
-1
@@ -1349,7 +1349,7 @@ impl Snapper {
|
||||
let unindexed = wires.segments().is_none();
|
||||
if unindexed {
|
||||
for w in wires.iter() {
|
||||
if wire_in_range(w) {
|
||||
if !w.is_display_only() && wire_in_range(w) {
|
||||
in_range_pts += w.points.len();
|
||||
in_range_wires.push(w);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user