From edbc4d8ff84cd7fe6128d38ec3716973094eeb08 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 2 Sep 2026 22:32:53 +0300 Subject: [PATCH] fix(render): correct lineweight display scaling Remove the global display boost and scale paper-space lineweights with the current sheet zoom across wire, block-wire, and hatch paths. Fixes #962 --- src/scene/pipeline/uniforms.rs | 7 ++++--- src/scene/view/render.rs | 21 ++++++++++----------- src/shaders/block_wire.wgsl | 4 ++-- src/shaders/hatch.wgsl | 9 +++++++-- src/shaders/hatch_texture.wgsl | 9 +++++++-- src/shaders/wire.wgsl | 4 ++-- src/shaders/wire_indexed.wgsl | 4 ++-- 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/scene/pipeline/uniforms.rs b/src/scene/pipeline/uniforms.rs index c493081bc..2d2b59420 100644 --- a/src/scene/pipeline/uniforms.rs +++ b/src/scene/pipeline/uniforms.rs @@ -25,8 +25,9 @@ pub struct Uniforms { /// zooming inside MSPACE changes one scalar instead of re-tessellating and /// re-uploading every dashed wire. pub linetype_scale: f32, - /// Pads the struct to the uniform alignment required by wgpu. - pub _pad: f32, + /// Multiplier for screen-space lineweights. Model space keeps `1.0`; + /// paper layouts derive it from the current paper zoom. + pub lineweight_scale: f32, // ── Relative-to-eye (double-single) additions ─────────────────────────── // Appended at the end so existing field offsets are unchanged; shaders that @@ -116,7 +117,7 @@ impl Uniforms { flat_shade: 0.0, transparency_enable: 1.0, linetype_scale: 1.0, - _pad: 0.0, + lineweight_scale: 1.0, view_rot: camera.view_proj_rte(bounds), eye_high, _pad_eh: 0.0, diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 2365ab5b8..180c9943d 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -23,6 +23,7 @@ use crate::scene::{ }; const DISPLAY_STYLE_CACHE_LIMIT: usize = if cfg!(target_arch = "wasm32") { 4 } else { 24 }; +const MM_TO_PX: f32 = 96.0 / 25.4; // ── Camera hover state (shader::Program::State) ─────────────────────────── @@ -2592,7 +2593,7 @@ impl Scene { wire.fill_tris_low.clear(); } if let Some(mm) = style.resolve_lineweight(wire.aci) { - wire.line_weight_px = (mm * (96.0 / 25.4) * 2.0).max(1.0); + wire.line_weight_px = (mm * MM_TO_PX).max(1.0); } for vertex in &mut wire.text_verts { self.apply_display_plot_style(&mut vertex.color, wire.aci, &style); @@ -2648,7 +2649,7 @@ impl Scene { } } if let Some(mm) = style.resolve_lineweight(hatch.aci) { - hatch.line_weight_px = (mm * (96.0 / 25.4) * 2.0).max(1.0); + hatch.line_weight_px = (mm * MM_TO_PX).max(1.0); } if let crate::scene::model::hatch_model::HatchPattern::Gradient { color2, .. @@ -2710,7 +2711,7 @@ impl Scene { self.apply_display_plot_style(&mut color, wire.aci, &style); let line_weight_px = style .resolve_lineweight(wire.aci) - .map(|mm| (mm * (96.0 / 25.4) * 2.0).max(1.0)) + .map(|mm| (mm * MM_TO_PX).max(1.0)) .unwrap_or(wire.line_weight_px); for (triangle_index, triangle) in wire.fill_tris.chunks_exact(3).enumerate() { let mut boundary = Vec::with_capacity(4); @@ -2943,15 +2944,8 @@ pub struct InheritStyle { /// Convert a concrete (already layer-resolved) lineweight to display pixels. pub(crate) fn lineweight_to_px(lw: &LineWeight) -> f32 { - const MM_TO_PX: f32 = 96.0 / 25.4; - // CAD apps display model-space lineweights larger than their true physical - // size so the gradations stay legible on screen — at true scale a 0.5 mm - // line is ~2 px and is indistinguishable from thinner weights (which all - // floor to 1 px). Apply the same legibility boost so weights are pronounced - // and tell apart, matching other DWG editors. (#147) - const LWT_DISPLAY_BOOST: f32 = 2.0; lw.millimeters() - .map(|mm| (mm as f32 * MM_TO_PX * LWT_DISPLAY_BOOST).max(1.0)) + .map(|mm| (mm as f32 * MM_TO_PX).max(1.0)) .unwrap_or(1.0) } @@ -3853,6 +3847,11 @@ impl Scene { full_bounds, self.document.header.lineweight_display || display_plot_lineweights, ); + if self.current_layout != "Model" { + let paper_per_pixel = 2.0 * self.camera.borrow().ortho_size() / canvas.1.max(1.0); + let paper_units_per_mm = self.paper_space_unit_factor() as f32; + uniforms.lineweight_scale = paper_units_per_mm / paper_per_pixel / MM_TO_PX; + } // Model space: scale linetypes using the current annotation scale so their // appearance can match a paper-space viewport at the same drawing scale. diff --git a/src/shaders/block_wire.wgsl b/src/shaders/block_wire.wgsl index 29e5bd256..5a12c20cb 100644 --- a/src/shaders/block_wire.wgsl +++ b/src/shaders/block_wire.wgsl @@ -5,7 +5,7 @@ struct Uniforms { flat_shade: f32, transparency_enable: f32, linetype_scale: f32, - _pad: f32, + lineweight_scale: f32, view_rot: mat4x4, eye_high: vec3, _pad_eh: f32, @@ -67,7 +67,7 @@ fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } - return select(0.5, px_hw, u.lwdisplay_enable > 0.5); + return select(0.5, max(px_hw * u.lineweight_scale, 0.5), u.lwdisplay_enable > 0.5); } fn marker_relative( diff --git a/src/shaders/hatch.wgsl b/src/shaders/hatch.wgsl index 33c6696b7..dba8b4309 100644 --- a/src/shaders/hatch.wgsl +++ b/src/shaders/hatch.wgsl @@ -14,7 +14,8 @@ struct Uniforms { lwdisplay_enable: f32, flat_shade: f32, transparency_enable: f32, - _pad: vec2, + _linetype_scale: f32, + lineweight_scale: f32, // Relative-to-eye (double-single): see wire.wgsl. view_rot: mat4x4, eye_high: vec3, @@ -162,7 +163,11 @@ fn check_family( -ddx_xz.x * sin_a + ddx_xz.y * cos_a, -ddy_xz.x * sin_a + ddy_xz.y * cos_a, )) * 0.5; - let width_px = select(1.0, max(fam.line_width, 1.0), u.lwdisplay_enable > 0.5); + let width_px = select( + 1.0, + max(fam.line_width * u.lineweight_scale, 1.0), + u.lwdisplay_enable > 0.5, + ); let half_line = half_px * width_px; // World units per screen pixel on each axis — used to light exactly the diff --git a/src/shaders/hatch_texture.wgsl b/src/shaders/hatch_texture.wgsl index de40284bc..515921d5d 100644 --- a/src/shaders/hatch_texture.wgsl +++ b/src/shaders/hatch_texture.wgsl @@ -13,7 +13,8 @@ struct Uniforms { lwdisplay_enable: f32, flat_shade: f32, transparency_enable: f32, - _pad: vec2, + _linetype_scale: f32, + lineweight_scale: f32, view_rot: mat4x4, eye_high: vec3, _pad_eh: f32, @@ -146,7 +147,11 @@ fn check_family( -ddx_xz.x * sin_a + ddx_xz.y * cos_a, -ddy_xz.x * sin_a + ddy_xz.y * cos_a, )) * 0.5; - let width_px = select(1.0, max(fam.line_width, 1.0), u.lwdisplay_enable > 0.5); + let width_px = select( + 1.0, + max(fam.line_width * u.lineweight_scale, 1.0), + u.lwdisplay_enable > 0.5, + ); let half_line = half_px * width_px; let wpx = length(vec2(ddx_xz.x, ddy_xz.x)); diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index c8fac6521..68995cc5b 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -29,7 +29,7 @@ struct Uniforms { // every line opaque. transparency_enable: f32, linetype_scale: f32, - _pad: f32, + lineweight_scale: f32, // ── Relative-to-eye (double-single) ────────────────────────────────── // view_rot is the rotation-only view-projection; vertices subtract the eye // (eye_high + eye_low, two f32 emulating f64) before transforming, so the @@ -102,7 +102,7 @@ struct VertexOut { fn resolve_hw(taper: f32, world_hw: f32, px_hw: f32) -> f32 { if taper > 0.0 { return max(taper / u.world_per_pixel, 0.5); } if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } - return select(0.5, px_hw, u.lwdisplay_enable > 0.5); + return select(0.5, max(px_hw * u.lineweight_scale, 0.5), u.lwdisplay_enable > 0.5); } fn marker_relative(position_high: vec3, position_low: vec3, instance: InstanceIn) -> vec3 { diff --git a/src/shaders/wire_indexed.wgsl b/src/shaders/wire_indexed.wgsl index c62d92bd6..241e3abe8 100644 --- a/src/shaders/wire_indexed.wgsl +++ b/src/shaders/wire_indexed.wgsl @@ -12,7 +12,7 @@ struct Uniforms { flat_shade: f32, transparency_enable: f32, linetype_scale: f32, - _pad: f32, + lineweight_scale: f32, view_rot: mat4x4, eye_high: vec3, _pad_eh: f32, @@ -81,7 +81,7 @@ fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { return max((taper_ratio * world_hw) / u.world_per_pixel, 0.5); } if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } - return select(0.5, px_hw, u.lwdisplay_enable > 0.5); + return select(0.5, max(px_hw * u.lineweight_scale, 0.5), u.lwdisplay_enable > 0.5); } fn marker_relative(position_high: vec3, position_low: vec3, c: WireConst) -> vec3 {