forked from 1b-agent/opencadstudio-upstream
fix(viewport): frame model-documentation drawing views correctly
A floating viewport with a real saved view — a non-zero view target, as a model documentation drawing view or a detail/section viewport carries — now uses that view directly instead of running the median-cluster overlap test. That test measured against the densest sub-cluster (a big symbol library beside the small view geometry) and wrongly auto-fit these viewports onto the library. A fully uninitialised view (target AND centre both zero) frames its saved origin and stays empty rather than auto-fitting the whole model in; the auto-fit rescue is kept only for a stale target=(0,0,0) paired with a non-zero pre-georeference centre. Also widen the restored-view ortho depth half-range (view_height ×4 → ×64): a thin projection such as a section/side view has content far deeper than its visible height (its cut plane sits ~half an object-depth from the view target), so the tighter range clipped the view outline behind the far plane. Orthographic depth precision is linear, so the wider range costs no z-fighting. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
b884e33f38
commit
251ad436d7
2 files changed
+52
-8
No files matched your search
@@ -242,10 +242,15 @@ impl Scene {
|
||||
projection: view::camera::Projection::Orthographic,
|
||||
yaw,
|
||||
pitch,
|
||||
// Sized from the saved view height rather than a zoom-scaled range,
|
||||
// so a restored view keeps stable depth precision. `view_height` is
|
||||
// the visible extent; a few multiples cover the drawing's depth.
|
||||
depth_half_range: (view_height as f32 * 4.0).max(1.0),
|
||||
// Sized from the saved view height (not a zoom-scaled range) so a
|
||||
// restored view keeps stable depth precision. A THIN projection —
|
||||
// e.g. a model-documentation section/side view whose cut plane sits
|
||||
// half an object-depth from the view target — has content much
|
||||
// deeper than its visible height, so the depth half-range must be a
|
||||
// generous multiple: `×4` clipped a section view's outline that was
|
||||
// ~6× the view height deep. Orthographic depth precision is linear,
|
||||
// so the wide range costs no z-fighting.
|
||||
depth_half_range: (view_height as f32 * 64.0).max(1.0),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+43
-4
@@ -324,6 +324,16 @@ impl Scene {
|
||||
let saved_h = vp.view_height.abs();
|
||||
let aspect_d = (vp.width / vp.height.max(1.0)).max(1e-9);
|
||||
let cluster_half = self.local_extent_max.max(1.0) as f64;
|
||||
// Full model-space bounds. The overlap test below accepts the saved view
|
||||
// when its frame touches ANY drawn geometry — not just the dense median
|
||||
// cluster (`local_center ± cluster_half`). A drawing with a second,
|
||||
// sparser cluster (e.g. model-documentation view geometry sitting apart
|
||||
// from a big symbol library) has viewports legitimately aimed at that
|
||||
// second cluster; testing only the dense one wrongly auto-fits them onto
|
||||
// the library. Fall back to the cluster box when no extents are known.
|
||||
let full_bounds = self.model_space_extents().map(|(mn, mx)| {
|
||||
(mn.x as f64, mn.y as f64, mx.x as f64, mx.y as f64)
|
||||
});
|
||||
// Absolute drawing centre. Geometry now reaches the scene at absolute
|
||||
// (UTM) coordinates — the old code centred the overlap test and the
|
||||
// auto-fit on the origin, which was right only while world_offset
|
||||
@@ -346,6 +356,25 @@ impl Scene {
|
||||
.unwrap_or((0.0, 0.0))
|
||||
};
|
||||
|
||||
// A non-zero `view_target` is a deliberately-aimed saved view (a model
|
||||
// documentation drawing view, a detail/section viewport, any viewport
|
||||
// panned onto a specific WCS point). It is authoritative — use it as-is.
|
||||
// The overlap test / auto-fit below only rescues the STALE default,
|
||||
// where `view_target == (0,0,0)` points at empty WCS while the model
|
||||
// sits at UTM. Guarding on the target avoids the tight median cluster
|
||||
// (which collapses onto the densest sub-cluster) wrongly rejecting a
|
||||
// valid view that frames a smaller, off-centre sub-cluster.
|
||||
let target_set =
|
||||
vp.view_target.x.abs() > 1e-6 || vp.view_target.y.abs() > 1e-6;
|
||||
// A fully-uninitialised view (target AND centre both zero) is a stale
|
||||
// placeholder viewport — frame its saved view (the origin) and leave it
|
||||
// empty rather than auto-fitting the whole model into it. The auto-fit
|
||||
// rescue is meant only for a stale target=(0,0,0) paired with a NON-zero
|
||||
// (pre-georeference) view_centre that points at empty WCS while the model
|
||||
// sits far away — that case still falls through to the overlap test.
|
||||
let center_set =
|
||||
vp.view_center.x.abs() > 1e-6 || vp.view_center.y.abs() > 1e-6;
|
||||
|
||||
if let Some(cam) = self.camera_from_view(
|
||||
vp.view_direction,
|
||||
vp.view_target,
|
||||
@@ -356,13 +385,23 @@ impl Scene {
|
||||
saved_h,
|
||||
vp.twist_angle,
|
||||
) {
|
||||
if target_set || !center_set {
|
||||
return Some(cam);
|
||||
}
|
||||
let half_h = saved_h * 0.5;
|
||||
let half_w = half_h * aspect_d;
|
||||
let (tx, ty) = (cam.target.x as f64, cam.target.y as f64);
|
||||
let overlaps = tx + half_w >= cx - cluster_half
|
||||
&& tx - half_w <= cx + cluster_half
|
||||
&& ty + half_h >= cy - cluster_half
|
||||
&& ty - half_h <= cy + cluster_half;
|
||||
// Prefer the true model bounds; fall back to the median cluster box.
|
||||
let (bx0, by0, bx1, by1) = full_bounds.unwrap_or((
|
||||
cx - cluster_half,
|
||||
cy - cluster_half,
|
||||
cx + cluster_half,
|
||||
cy + cluster_half,
|
||||
));
|
||||
let overlaps = tx + half_w >= bx0
|
||||
&& tx - half_w <= bx1
|
||||
&& ty + half_h >= by0
|
||||
&& ty - half_h <= by1;
|
||||
if overlaps {
|
||||
return Some(cam);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user