From 7ea4f338f6f6743cda8efc8355fe77441c1e2411 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 31 Aug 2026 22:48:57 +0300 Subject: [PATCH] fix(render): preserve transparency inheritance Resolve ByBlock alpha independently from color and keep explicit opaque values from falling back to layer transparency. Pin the matching cadcodec API across desktop, plugin, and web targets. Refs HakanSeven12/cadcodec#2 --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- crates/ocs_plugin_api/Cargo.toml | 4 ++-- crates/ocs_plugin_api/build.rs | 4 ++++ crates/ocs_web_worker/Cargo.toml | 2 +- src/entities/raster_image.rs | 2 +- src/scene/cache/block_cache.rs | 29 +++++++++++++++---------- src/scene/cache/properties.rs | 17 ++++++--------- src/scene/render_graph.rs | 19 ++++++++++++++-- src/scene/selection.rs | 11 +++++----- src/scene/view/dispatch.rs | 8 +++++-- src/scene/view/render.rs | 37 +++++++++++++++++++++++++------- 12 files changed, 94 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f85069b6b..7b66c0e6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.1" -source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=3a87743ad7b0fc74f0137ccd48f598116e2904b0#3a87743ad7b0fc74f0137ccd48f598116e2904b0" +source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=c91a1c1de89a0fbc006dc360ddb0251ac8c207d1#c91a1c1de89a0fbc006dc360ddb0251ac8c207d1" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 74f7792a8..79f2cf17a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3a87743ad7b0fc74f0137ccd48f598116e2904b0", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "c91a1c1de89a0fbc006dc360ddb0251ac8c207d1", features = ["serde"] } cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "48c634995fa60d3928c37bae49b12b421c56c886", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" @@ -61,7 +61,7 @@ iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aa iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } [patch."https://github.com/HakanSeven12/cadcodec.git"] -acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "3a87743ad7b0fc74f0137ccd48f598116e2904b0" } +acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "c91a1c1de89a0fbc006dc360ddb0251ac8c207d1" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/ocs_plugin_api/Cargo.toml b/crates/ocs_plugin_api/Cargo.toml index bd69e4060..49e520059 100644 --- a/crates/ocs_plugin_api/Cargo.toml +++ b/crates/ocs_plugin_api/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] } # Pulled in only by the `host` feature, which adds the `acadrust`-typed # `HostApi` runtime surface. The default crate stays dependency-free so engine # crates and external tooling can depend on the manifest/ribbon contract cheaply. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3a87743ad7b0fc74f0137ccd48f598116e2904b0", optional = true, features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "c91a1c1de89a0fbc006dc360ddb0251ac8c207d1", optional = true, features = ["serde"] } # Runtime IPC and serialization (host feature only). interprocess = { version = "2", optional = true } @@ -37,7 +37,7 @@ serde_json = "1" serde = { version = "1", features = ["derive"] } cargo-lock = "11" # acadrust is scanned at build time to generate the embedded type registry. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3a87743ad7b0fc74f0137ccd48f598116e2904b0", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "c91a1c1de89a0fbc006dc360ddb0251ac8c207d1", features = ["serde"] } [dev-dependencies] serde_json = "1" diff --git a/crates/ocs_plugin_api/build.rs b/crates/ocs_plugin_api/build.rs index d818a0dd6..87b9bafb9 100644 --- a/crates/ocs_plugin_api/build.rs +++ b/crates/ocs_plugin_api/build.rs @@ -123,6 +123,10 @@ fn add_enum_samples(tracer: &mut Tracer, samples: &mut Samples) { let _ = tracer.trace_value(samples, &acadrust::LineWeight::Default); let _ = tracer.trace_value(samples, &acadrust::LineWeight::Value(0)); + let _ = tracer.trace_value(samples, &acadrust::Transparency::BY_LAYER); + let _ = tracer.trace_value(samples, &acadrust::Transparency::BY_BLOCK); + let _ = tracer.trace_value(samples, &acadrust::Transparency::OPAQUE); + let _ = tracer.trace_value(samples, &acadrust::entities::SmoothSurfaceType::None); let _ = tracer.trace_value( samples, diff --git a/crates/ocs_web_worker/Cargo.toml b/crates/ocs_web_worker/Cargo.toml index bef08417a..a700c08c3 100644 --- a/crates/ocs_web_worker/Cargo.toml +++ b/crates/ocs_web_worker/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3a87743ad7b0fc74f0137ccd48f598116e2904b0", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "c91a1c1de89a0fbc006dc360ddb0251ac8c207d1", features = ["serde"] } bincode = "1.3" serde = { version = "1", features = ["derive"] } console_error_panic_hook = "0.1" diff --git a/src/entities/raster_image.rs b/src/entities/raster_image.rs index 51937cf58..c91aaf793 100644 --- a/src/entities/raster_image.rs +++ b/src/entities/raster_image.rs @@ -290,7 +290,7 @@ impl PropertyEditable for RasterImage { .flags .contains(acadrust::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY); let clip_inverted = self.clip_boundary.clip_mode == acadrust::entities::ClipMode::Inside; - let transparency = format!("{:.0}%", self.common.transparency.as_percent() * 100.0); + let transparency = self.common.transparency.to_string(); vec![ PropSection { title: t!("Geometry").into_owned(), diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index f38ba11c5..aaf12f4da 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -88,6 +88,7 @@ pub struct LocalWire { /// `points.is_empty() && !fill_tris.is_empty()`. pub is_fill_only: bool, pub color_is_byblock: bool, + pub transparency_is_byblock: bool, pub lt_is_byblock: bool, pub lw_is_byblock: bool, /// Set when this child sits on layer "0" and the matching property is @@ -710,7 +711,8 @@ fn tessellate_sub_local( .unwrap_or(true); let color_l0 = !has_book_color && on_l0 && sub.common().color == AcadColor::ByLayer; - let transparency_l0 = on_l0 && sub.common().transparency.alpha() == 0; + let transparency_is_byblock = sub.common().transparency.is_by_block(); + let transparency_l0 = on_l0 && sub.common().transparency.is_by_layer(); let lt_l0 = on_l0 && { let lt = &sub.common().linetype; lt.is_empty() || lt.eq_ignore_ascii_case("bylayer") @@ -846,10 +848,11 @@ fn tessellate_sub_local( hide_unselected: frame_mode == Some(0), is_fill_only, color_is_byblock: color_is_byblock && wire_on_base_color, + transparency_is_byblock, lt_is_byblock, lw_is_byblock, color_l0: color_l0 && wire_on_base_color, - transparency_l0: transparency_l0 && wire_on_base_color, + transparency_l0, lt_l0, lw_l0, aabb_local, @@ -1990,24 +1993,28 @@ fn expand_defn( /// layer-0 rule → insert-layer colour, else the cached colour; finally xref /// fade. Shared by the stroke, fill, and greeked-text emit paths. fn resolve_wire_color(lw: &LocalWire, ctx: &ExpandCtx) -> [f32; 4] { - let c = if ctx.selected { + let mut color = if ctx.selected { WireModel::SELECTED } else if lw.color_is_byblock { ctx.ins_color } else if lw.color_l0 { - let alpha = if lw.transparency_l0 { + ctx.l0.color + } else { + lw.color + }; + if !ctx.selected { + color[3] = if lw.transparency_is_byblock { + ctx.ins_color[3] + } else if lw.transparency_l0 { ctx.l0.color[3] } else { lw.color[3] }; - [ctx.l0.color[0], ctx.l0.color[1], ctx.l0.color[2], alpha] - } else { - lw.color - }; + } if ctx.is_xref && !ctx.selected { - fade_toward_bg(c, ctx.bg_color) + fade_toward_bg(color, ctx.bg_color) } else { - c + color } } @@ -2592,7 +2599,7 @@ fn emit_wire( pos: [hx, hy, hz], pos_low: [lx, ly, lz], uv: tv.uv, - color: [rgb[0], rgb[1], rgb[2], tv.color[3]], + color: [rgb[0], rgb[1], rgb[2], final_color[3]], draw_depth: tv.draw_depth, }); } diff --git a/src/scene/cache/properties.rs b/src/scene/cache/properties.rs index 8b3d04711..1e463ccd8 100644 --- a/src/scene/cache/properties.rs +++ b/src/scene/cache/properties.rs @@ -1,4 +1,4 @@ -use acadrust::{EntityType, Handle}; +use acadrust::{EntityType, Handle, Transparency}; use crate::t; use crate::scene::model::object::{PropSection, PropValue, Property}; @@ -10,15 +10,12 @@ pub fn general_section(entity: &EntityType) -> PropSection { } else { common.linetype.clone() }; - // Alpha 0 is ByLayer, Alpha 1 is ByBlock; show them by name and fall back - // to a rounded percentage for explicit values. - let transp_display = match common.transparency.alpha() { - 0 => "ByLayer".to_string(), - 1 => "ByBlock".to_string(), - alpha => format!( - "{}", - (alpha as f64 / 255.0 * 100.0).round() as u32 - ), + let transp_display = match common.transparency { + Transparency::ByLayer => "ByLayer".to_string(), + Transparency::ByBlock => "ByBlock".to_string(), + Transparency::Explicit(alpha) => { + ((alpha as f64 / 255.0 * 100.0).round() as u32).to_string() + } }; // Hyperlink is stored in XDATA under the "PE_URL" application. diff --git a/src/scene/render_graph.rs b/src/scene/render_graph.rs index dd737aaef..69400ad4f 100644 --- a/src/scene/render_graph.rs +++ b/src/scene/render_graph.rs @@ -67,6 +67,8 @@ pub struct InsertStyleSpec { own: BlockStyle, color_byblock: bool, color_bylayer: bool, + transparency_byblock: bool, + transparency_bylayer: bool, linetype_byblock: bool, linetype_bylayer: bool, lineweight_byblock: bool, @@ -83,6 +85,8 @@ impl InsertStyleSpec { own: BlockStyle::for_entity(document, &entity, viewport), color_byblock: !has_book_color && insert.common.color == Color::ByBlock, color_bylayer: !has_book_color && insert.common.color == Color::ByLayer, + transparency_byblock: insert.common.transparency.is_by_block(), + transparency_bylayer: insert.common.transparency.is_by_layer(), linetype_byblock: linetype.eq_ignore_ascii_case("byblock"), linetype_bylayer: linetype.is_empty() || linetype.eq_ignore_ascii_case("bylayer"), lineweight_byblock: matches!( @@ -101,12 +105,23 @@ impl InsertStyleSpec { pub fn resolve(self, parent: BlockStyle) -> BlockStyle { let mut insert = self.own.insert; if self.color_byblock { - insert.0 = parent.insert.0; + insert.0[0] = parent.insert.0[0]; + insert.0[1] = parent.insert.0[1]; + insert.0[2] = parent.insert.0[2]; insert.4 = parent.insert.4; } else if self.layer0 && self.color_bylayer { - insert.0 = parent.layer0.color; + insert.0[0] = parent.layer0.color[0]; + insert.0[1] = parent.layer0.color[1]; + insert.0[2] = parent.layer0.color[2]; insert.4 = parent.layer0_aci; } + insert.0[3] = if self.transparency_byblock { + parent.insert.0[3] + } else if self.layer0 && self.transparency_bylayer { + parent.layer0.color[3] + } else { + insert.0[3] + }; if self.linetype_byblock { insert.1 = parent.insert.1; insert.2 = parent.insert.2; diff --git a/src/scene/selection.rs b/src/scene/selection.rs index 925b7d93d..0c67902e7 100644 --- a/src/scene/selection.rs +++ b/src/scene/selection.rs @@ -675,11 +675,12 @@ impl Scene { .to_string(), ), "lineweight" => Some(Self::format_lineweight(entity.common().line_weight)), - "transparency" => Some(if entity.common().transparency.alpha() == 0 { - "ByLayer".to_string() - } else { - ((entity.common().transparency.alpha() as f64 / 255.0 * 100.0).round() as u32) - .to_string() + "transparency" => Some(match entity.common().transparency { + acadrust::types::Transparency::ByLayer => "ByLayer".to_string(), + acadrust::types::Transparency::ByBlock => "ByBlock".to_string(), + acadrust::types::Transparency::Explicit(alpha) => { + ((alpha as f64 / 255.0 * 100.0).round() as u32).to_string() + } }), "hyperlink" => Some( entity diff --git a/src/scene/view/dispatch.rs b/src/scene/view/dispatch.rs index 34c441329..4c47da101 100644 --- a/src/scene/view/dispatch.rs +++ b/src/scene/view/dispatch.rs @@ -137,9 +137,13 @@ pub fn apply_common_prop(entity: &mut EntityType, field: &str, value: &str) { "transparency" => { let s = value.trim(); if s.eq_ignore_ascii_case("ByLayer") { - entity.as_entity_mut().set_transparency(Transparency::new(0)); + entity + .as_entity_mut() + .set_transparency(Transparency::BY_LAYER); } else if s.eq_ignore_ascii_case("ByBlock") { - entity.as_entity_mut().set_transparency(Transparency::new(1)); + entity + .as_entity_mut() + .set_transparency(Transparency::BY_BLOCK); } else { let clean = s.trim_end_matches('%').trim(); if let Ok(pct) = clean.parse::() { diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index c2d8d2f2b..de64fc1df 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -2863,7 +2863,7 @@ pub(crate) fn render_style_for_viewport( _ => 0, }; let [r, g, b, _] = tess_util::aci_to_rgba(resolved); - let transparency = if common.transparency.alpha() == 0 { + let transparency = if common.transparency.is_by_layer() { viewport_override( document, layer_name, @@ -3077,18 +3077,21 @@ pub(crate) fn render_style_for_block_sub_viewport( let on_l0 = is_effective_layer_zero(&common.layer); let has_book_color = has_resolved_book_color(document, e); - let final_color = if !has_book_color && common.color == AcadColor::ByBlock { + let resolved_rgb = if !has_book_color && common.color == AcadColor::ByBlock { insert_color } else if !has_book_color && on_l0 && common.color == AcadColor::ByLayer { - let alpha = if common.transparency.alpha() == 0 { - l0.color[3] - } else { - color[3] - }; - [l0.color[0], l0.color[1], l0.color[2], alpha] + l0.color } else { color }; + let alpha = if common.transparency.is_by_block() { + insert_color[3] + } else if on_l0 && common.transparency.is_by_layer() { + l0.color[3] + } else { + color[3] + }; + let final_color = [resolved_rgb[0], resolved_rgb[1], resolved_rgb[2], alpha]; let lt_bylayer = common.linetype.is_empty() || common.linetype.eq_ignore_ascii_case("bylayer"); @@ -4319,4 +4322,22 @@ mod layer0_inherit_tests { assert_eq!(&c[..3], &walls[..3], "RGB inherited from the insert layer"); assert!((c[3] - 0.5).abs() < 0.02, "child's own 50% transparency is kept, got {}", c[3]); } + + #[test] + fn byblock_transparency_inherits_insert_alpha() { + let d = doc(); + let mut entity = child("Other", Color::Index(3)); + entity.common_mut().transparency = Transparency::BY_BLOCK; + let color = resolve(&d, &entity, [0.2, 0.4, 0.6, 0.25]); + assert_eq!(color[3], 0.25); + } + + #[test] + fn explicit_opaque_does_not_inherit_alpha() { + let d = doc(); + let mut entity = child("0", Color::ByLayer); + entity.common_mut().transparency = Transparency::OPAQUE; + let color = resolve(&d, &entity, [0.2, 0.4, 0.6, 0.25]); + assert_eq!(color[3], 1.0); + } }