fix(view): Zoom Extents and All frame a drawing placed at survey coordinates (#1829)

A block whose content is only 3D solids leaves a marker wire carrying
its insertion snap, and that point counted as drawing extent: a
georeferenced block inserted at the origin made ZOOM E frame the
origin and the model together, 860 km wide. Extents now skip those
markers; the solids count through their meshes. The marker's snap also
took the insertion point straight from the OCS, so a mirrored insert's
snap sat on the far side of the origin.

ZOOM All fitted the limits alone. In a 3D view it is now Zoom
Extents, and in plan it frames the limits together with the extents.

Instanced solids summed their rotated point and translation in f32
before meeting the eye, which cost up to half a metre at survey
coordinates; the large terms now cancel first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-16 02:11:16 +03:00
1 parent 9a567d4f44
commit f6455b22eb
5 files changed
+61 -25

No files matched your search

+3 -5
View File
@@ -329,11 +329,9 @@ crate::impl_entity_basics!(Insert);
impl crate::entities::traits::FallbackTess for Insert {
fn fallback_geometry(&self) -> crate::scene::convert::tess_util::FallbackGeometry {
let (ipx, ipy, ipz) = (
self.insert_point.x,
self.insert_point.y,
self.insert_point.z,
);
// `insert_point` is in the OCS of `normal`.
let w = Matrix3::arbitrary_axis(self.normal) * self.insert_point;
let (ipx, ipy, ipz) = (w.x, w.y, w.z);
let ip = Vec3::new(ipx as f32, ipy as f32, ipz as f32);
let s = 0.1_f64;
let pts = vec![
+14 -3
View File
@@ -1722,9 +1722,20 @@ impl Scene {
// Keep their base points as a fallback for otherwise empty drawings.
let mut infinite_base: Vec<glam::DVec3> = Vec::new();
for wire in wires.iter() {
let is_infinite = Self::handle_from_wire_name(&wire.name)
.and_then(|handle| self.document.get_entity(handle))
.is_some_and(|entity| matches!(entity, EntityType::XLine(_) | EntityType::Ray(_)));
let entity = Self::handle_from_wire_name(&wire.name)
.and_then(|handle| self.document.get_entity(handle));
// A block whose content draws no wire (3D solids only) leaves a
// marker carrying just its insertion snap. The content counts
// through its meshes; the insertion point is no geometry, and a
// georeferenced block's sits far from what it draws (#1829).
if matches!(entity, Some(EntityType::Insert(_)))
&& wire.points.is_empty()
&& wire.text_verts.is_empty()
{
continue;
}
let is_infinite =
entity.is_some_and(|entity| matches!(entity, EntityType::XLine(_) | EntityType::Ray(_)));
if is_infinite {
infinite_base.extend(
wire.key_vertices
+4 -6
View File
@@ -1185,11 +1185,9 @@ fn tessellate_entity_inner(
}
if let EntityType::Insert(ins) = e {
let ip = glam::Vec3::new(
(ins.insert_point.x) as f32,
(ins.insert_point.y) as f32,
(ins.insert_point.z) as f32,
);
// `insert_point` is in the OCS of `normal`.
let w = codec::types::Matrix3::arbitrary_axis(ins.normal) * ins.insert_point;
let ip = glam::DVec3::new(w.x, w.y, w.z);
let marker = WireModel {
bg_adapt: None,
point_marker: None,
@@ -1217,7 +1215,7 @@ fn tessellate_entity_inner(
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
snap_pts: vec![(ip.as_dvec3(), model::wire_model::SnapHint::Insertion)],
snap_pts: vec![(ip, model::wire_model::SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
+23 -4
View File
@@ -120,16 +120,35 @@ impl Scene {
.remove(&self.current_layout);
}
/// ZOOM All frames the configured drawing limits. Object-only framing
/// remains the responsibility of ZOOM Extents.
/// ZOOM All frames the drawing limits together with what the drawing
/// draws.
pub fn fit_all_with_limits(&mut self) {
let Some((limit_min, limit_max)) = self.current_drawing_limits() else {
self.fit_all();
return;
};
// In a 3D view ZOOM All is Zoom Extents. In plan it shows the larger
// of the limits and the extents, so geometry outside the limits (a
// drawing placed at survey coordinates) stays on screen (#1829).
// ponytail: plan means looking down world Z; a rotated UCS plan view
// counts as 3D and gets Zoom Extents.
let eye = self.camera.borrow().rotation * glam::Vec3::Z;
if self.active_viewport.is_none() && eye.z.abs() < 0.999 {
self.fit_all();
return;
}
let min = glam::Vec3::new(limit_min.x as f32, limit_min.y as f32, 0.0);
let max = glam::Vec3::new(limit_max.x as f32, limit_max.y as f32, 0.0);
let mut min = glam::Vec3::new(limit_min.x as f32, limit_min.y as f32, 0.0);
let mut max = glam::Vec3::new(limit_max.x as f32, limit_max.y as f32, 0.0);
let block = if self.current_layout == "Model" || self.active_viewport.is_some() {
self.model_space_block_handle()
} else {
self.current_layout_block_handle()
};
if let Some((lo, hi)) = self.visible_block_bounds(block) {
min = min.min(lo.as_vec3());
max = max.max(hi.as_vec3());
}
// MSPACE owns a camera encoded on the active viewport entity.
if self.active_viewport.is_some() {
+17 -7
View File
@@ -153,17 +153,27 @@ fn relative_position(
position_low: vec3<f32>,
instance: MeshInstance,
) -> vec3<f32> {
let world_high = vec3<f32>(
dot(instance.model_row_0.xyz, position) + instance.model_row_0.w,
dot(instance.model_row_1.xyz, position) + instance.model_row_1.w,
dot(instance.model_row_2.xyz, position) + instance.model_row_2.w,
// Large terms cancel before small ones join: the translation against the
// eye first, then the rotated point, so neither a survey-placed instance
// nor a block drawn at survey coordinates loses its low bits to an f32
// sum (it cost up to half a metre).
let translation_high = vec3<f32>(
instance.model_row_0.w,
instance.model_row_1.w,
instance.model_row_2.w,
);
let world_low = vec3<f32>(
let rotated_high = vec3<f32>(
dot(instance.model_row_0.xyz, position),
dot(instance.model_row_1.xyz, position),
dot(instance.model_row_2.xyz, position),
);
let rotated_low = vec3<f32>(
dot(instance.model_row_0.xyz, position_low),
dot(instance.model_row_1.xyz, position_low),
dot(instance.model_row_2.xyz, position_low),
) + instance.translation_low.xyz;
return (world_high - u.eye_high) + (world_low - u.eye_low);
);
return ((translation_high - u.eye_high) + rotated_high)
+ (rotated_low + instance.translation_low.xyz - u.eye_low);
}
fn mesh_instance(input: InstanceIn) -> MeshInstance {