fix(point): preserve relative marker sizing

Keep rendering, picking, block transforms, and plotted output on the
same viewport-relative PDSIZE geometry without overloading wire width
fields.
This commit is contained in:
Hakan Seven committed 2026-08-22 22:22:01 +03:00
1 parent c5fe70627a
commit 7293bec1b9
69 files changed
+823 -221

No files matched your search

+5
View File
@@ -2066,6 +2066,7 @@ fn tessellate_dimension_inner(
let (ext1, ext2) = split_ext_lines(&geom.ext_lines);
if !ext1.is_empty() {
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -2099,6 +2100,7 @@ fn tessellate_dimension_inner(
}
if !ext2.is_empty() {
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -2132,6 +2134,7 @@ fn tessellate_dimension_inner(
}
} else {
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -2166,6 +2169,7 @@ fn tessellate_dimension_inner(
}
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -2216,6 +2220,7 @@ fn tessellate_dimension_inner(
aci_to_rgba(&c)
};
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
+2
View File
@@ -674,6 +674,7 @@ impl LeaderTess for Leader {
if verts.len() < 2 {
return WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -852,6 +853,7 @@ impl LeaderTess for Leader {
crate::scene::convert::tessellate::points_to_ds(fill_tris);
WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
+5
View File
@@ -1503,6 +1503,7 @@ impl MultiLeaderTess for MultiLeader {
// WireModels so the renderer respects per-piece coloring.
let mut wires: Vec<WireModel> = Vec::new();
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -1842,6 +1843,7 @@ impl MultiLeaderTess for MultiLeader {
xy = xy.max(p[1] as f64);
}
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -1914,6 +1916,7 @@ impl MultiLeaderTess for MultiLeader {
pts.push([bx, by, z]);
}
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -2001,6 +2004,7 @@ impl MultiLeaderTess for MultiLeader {
wcs_corners[3],
];
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
@@ -2043,6 +2047,7 @@ impl MultiLeaderTess for MultiLeader {
wcs_corners[0],
];
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
+78 -82
View File
@@ -1,13 +1,15 @@
use acadrust::entities::Point;
use acadrust::EntityType;
use crate::t;
use cadkernel::geom2d::{Circle, Curve, Line};
use cadkernel::space::{curve::bezier_points, PlanarCurve, Plane, Vec3};
use crate::t;
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, square_grip};
use crate::entities::traits::RenderConvertible;
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
use crate::scene::model::object::{GripApply, GripDef, PropSection};
use crate::scene::model::wire_model::SnapHint;
use crate::scene::model::wire_model::{PointMarker, SnapHint};
/// Resolve a positive (absolute) PDSIZE to a world size. Relative/zero PDSIZE
/// is handled by [`relative_render`].
@@ -99,7 +101,7 @@ pub fn relative_world_size(pdsize: f64, wpp: f32, viewport_height_px: f32) -> f6
pub fn relative_marker_spec(
entity: &EntityType,
document: &acadrust::CadDocument,
) -> Option<(f32, [f32; 3])> {
) -> Option<PointMarker> {
let EntityType::Point(pt) = entity else {
return None;
};
@@ -107,27 +109,41 @@ pub fn relative_marker_spec(
if size > 0.0 || effective_pdmode(pt, document.header.point_display_mode) == 0 {
return None;
}
let n = point_normal(pt);
Some((
if size == 0.0 { 5.0 } else { -size as f32 },
[n.x as f32, n.y as f32, n.z as f32],
))
let plane = point_plane(pt);
let normal = Vec3::from(plane.normal().unwrap_or([0.0, 0.0, 1.0]));
Some(PointMarker {
origin: glam::DVec3::new(pt.location.x, pt.location.y, pt.location.z),
normal: glam::DVec3::new(normal.x, normal.y, normal.z),
axis_x: glam::DVec3::from_array(plane.x_axis),
axis_y: glam::DVec3::from_array(plane.y_axis),
viewport_percent: if size == 0.0 { 5.0 } else { -size as f32 },
})
}
fn point_normal(pt: &Point) -> glam::DVec3 {
glam::DVec3::new(pt.normal.x, pt.normal.y, pt.normal.z).normalize_or(glam::DVec3::Z)
let normal = Vec3::new(pt.normal.x, pt.normal.y, pt.normal.z)
.normalize()
.unwrap_or(Vec3::Z);
glam::DVec3::new(normal.x, normal.y, normal.z)
}
fn point_axes(pt: &Point) -> (glam::DVec3, glam::DVec3, glam::DVec3) {
let n = point_normal(pt);
let (ax, ay) = crate::scene::view::transform::ocs_axes((n.x, n.y, n.z));
let base_x = glam::DVec3::new(ax.0, ax.1, ax.2);
let base_y = glam::DVec3::new(ay.0, ay.1, ay.2);
let (sin, cos) = pt.x_axis_angle.to_radians().sin_cos();
(
base_x * cos + base_y * sin,
-base_x * sin + base_y * cos,
n,
fn point_plane(pt: &Point) -> Plane {
let origin = [pt.location.x, pt.location.y, pt.location.z];
let normal = Vec3::new(pt.normal.x, pt.normal.y, pt.normal.z)
.normalize()
.unwrap_or(Vec3::Z);
let x_seed = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 {
Vec3::Y.cross(normal)
} else {
Vec3::Z.cross(normal)
};
let base = Plane::orthonormal(origin, x_seed.to_array(), normal.to_array())
.unwrap_or(Plane::XY);
let (sin, cos) = pt.x_axis_angle.sin_cos();
Plane::from_axes(
origin,
base.vector_at([cos, sin]),
base.vector_at([-sin, cos]),
)
}
@@ -144,88 +160,68 @@ fn point_glyph(pt: &Point, pdmode: i16, s_half: f64) -> Vec<[f64; 3]> {
// The '+' and '×' arms reach the full PDSIZE (twice the radius), so the
// cross pokes out past any enclosing circle/square, which sit at the radius.
let arm = 2.0 * s_half;
let nan = [f64::NAN, f64::NAN, f64::NAN];
let center = glam::DVec3::new(pt.location.x, pt.location.y, pt.location.z);
let (axis_x, axis_y, normal) = point_axes(pt);
let map = |x: f64, y: f64, height: f64| {
let p = center + axis_x * x + axis_y * y + normal * height;
[p.x, p.y, p.z]
};
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut push_seg = |a: [f64; 3], b: [f64; 3]| {
if !pts.is_empty() {
pts.push(nan);
}
pts.push(a);
pts.push(b);
};
let mut curves = Vec::new();
let line = |start, end| Curve::Line(Line { start, end });
match shape {
// 0 = single dot — emit a tiny "+" so it's visible at any zoom.
0 => {
let d = s * 0.05;
push_seg(map(-d, 0.0, 0.0), map(d, 0.0, 0.0));
push_seg(map(0.0, -d, 0.0), map(0.0, d, 0.0));
curves.push(line([-d, 0.0], [d, 0.0]));
curves.push(line([0.0, -d], [0.0, d]));
}
1 => {} // explicit nothing
1 => {}
2 => {
push_seg(map(-arm, 0.0, 0.0), map(arm, 0.0, 0.0));
push_seg(map(0.0, -arm, 0.0), map(0.0, arm, 0.0));
curves.push(line([-arm, 0.0], [arm, 0.0]));
curves.push(line([0.0, -arm], [0.0, arm]));
}
3 => {
push_seg(map(-arm, -arm, 0.0), map(arm, arm, 0.0));
push_seg(map(-arm, arm, 0.0), map(arm, -arm, 0.0));
curves.push(line([-arm, -arm], [arm, arm]));
curves.push(line([-arm, arm], [arm, -arm]));
}
4 => {
// Upward tick rising from the point (length = PDSIZE/2), not a
// vertical line centred on it.
push_seg(map(0.0, 0.0, 0.0), map(0.0, s, 0.0));
curves.push(line([0.0, 0.0], [0.0, s]));
}
_ => {
push_seg(map(-s, 0.0, 0.0), map(s, 0.0, 0.0));
push_seg(map(0.0, -s, 0.0), map(0.0, s, 0.0));
curves.push(line([-s, 0.0], [s, 0.0]));
curves.push(line([0.0, -s], [0.0, s]));
}
}
if circle {
const N: usize = 64;
let mut ring: Vec<[f64; 3]> = Vec::with_capacity(N + 1);
for i in 0..=N {
let a = i as f64 * std::f64::consts::TAU / N as f64;
ring.push(map(a.cos() * s, a.sin() * s, 0.0));
}
if !pts.is_empty() {
pts.push(nan);
}
pts.extend(ring);
curves.push(Curve::Circle(Circle {
centre: [0.0, 0.0],
radius: s,
}));
}
if square {
let p1 = map(-s, -s, 0.0);
let p2 = map(s, -s, 0.0);
let p3 = map(s, s, 0.0);
let p4 = map(-s, s, 0.0);
if !pts.is_empty() {
pts.push(nan);
}
pts.extend_from_slice(&[p1, p2, p3, p4, p1]);
curves.push(line([-s, -s], [s, -s]));
curves.push(line([s, -s], [s, s]));
curves.push(line([s, s], [-s, s]));
curves.push(line([-s, s], [-s, -s]));
}
if pt.thickness.abs() > 1.0e-10 && !pts.is_empty() {
let base = pts.clone();
pts.push(nan);
pts.extend(base.iter().map(|p| {
if p[0].is_finite() {
[
p[0] + normal.x * pt.thickness,
p[1] + normal.y * pt.thickness,
p[2] + normal.z * pt.thickness,
]
} else {
nan
}
let plane = point_plane(pt);
let nan = [f64::NAN; 3];
let mut paths: Vec<Vec<[f64; 3]>> = curves
.iter()
.map(|curve| PlanarCurve::new(plane, curve.clone()).tessellate(64.0 / std::f64::consts::TAU))
.collect();
if pt.thickness.abs() > 1.0e-10 && !curves.is_empty() {
let normal = Vec3::from(plane.normal().unwrap_or([0.0, 0.0, 1.0]));
let top_origin = (Vec3::from(plane.origin) + normal * pt.thickness).to_array();
let top_plane = Plane::from_axes(top_origin, plane.x_axis, plane.y_axis);
paths.extend(curves.iter().map(|curve| {
PlanarCurve::new(top_plane, curve.clone())
.tessellate(64.0 / std::f64::consts::TAU)
}));
pts.push(nan);
pts.push(map(0.0, 0.0, 0.0));
pts.push(map(0.0, 0.0, pt.thickness));
paths.push(bezier_points(&[plane.origin, top_origin], 1));
}
pts
let mut points = Vec::new();
for path in paths {
if !points.is_empty() {
points.push(nan);
}
points.extend(path);
}
points
}
fn is_defpoints_layer(layer: &str) -> bool {
+1
View File
@@ -1667,6 +1667,7 @@ pub fn tessellate_table(
let mk =
|color: [f32; 4], points: Vec<[f32; 3]>, fill_tris: Vec<[f32; 3]>, lw: f32| -> WireModel {
WireModel {
point_marker: None,
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,