forked from 1b-agent/opencadstudio-upstream
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a567d4f44
commit
f6455b22eb
5 files changed
+61
-25
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user