forked from 1b-agent/opencadstudio-upstream
fix(blocks): layer-0 children inherit the block instance layer (#221)
Entities inside a block definition that sit on layer "0" must take on the properties of the layer the block reference is inserted on; every other layer is "sticky" and keeps its own identity. OpenCADStudio resolved a block child's ByLayer color/linetype/lineweight against the child's own layer in all cases, so layer-0 content stayed layer-0 (white) instead of inheriting the instance layer. Resolution now follows the standard rule for block children — explicit wins, then ByBlock → the insert's own style, then layer-0 + ByLayer → the insert's *layer* style, else the child's own layer. The insert's layer style threads through the whole block-expansion chain (and chains correctly through nested inserts), with the child's own transparency preserved. Covered across every block-render path: - wires (line/arc/circle/polyline/text) via the block cache + the explode fallback + insert attributes - hatch / solid fills (also fixes the PDF/print export path) - 3D solid block children (per-instance mesh recolor) - Dimension / Table baked-block sub-entities New shared helpers in scene::view::render: InheritStyle, layer_render_style, lineweight_to_px; render_style_for_block_sub gained the layer-0 branch. Adds unit tests covering the layer-0, sticky-layer, ByBlock, explicit and transparency cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a5d85aa8a
commit
970ee5a09f
6 files changed
+497
-64
No files matched your search
@@ -133,6 +133,7 @@ pub(crate) fn append_insert_attribute_wires(
|
||||
ins_pat_len: f32,
|
||||
ins_pat: [f32; 8],
|
||||
ins_lw_px: f32,
|
||||
ins_layer: render::InheritStyle,
|
||||
bg_color: [f32; 4],
|
||||
is_xref: bool,
|
||||
pslt_factor: f32,
|
||||
@@ -162,6 +163,7 @@ pub(crate) fn append_insert_attribute_wires(
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
);
|
||||
let sub_color = render::adapt_to_bg(sub_color, bg_color);
|
||||
let sub_color = if is_xref && !sel {
|
||||
|
||||
Vendored
+102
-37
@@ -59,6 +59,13 @@ pub struct LocalWire {
|
||||
pub color_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
|
||||
/// ByLayer. At expand time the value is then taken from the INSERT's
|
||||
/// *layer* (the layer-0 inheritance rule) instead of the cached layer-0
|
||||
/// value baked here.
|
||||
pub color_l0: bool,
|
||||
pub lt_l0: bool,
|
||||
pub lw_l0: bool,
|
||||
/// XY bounding box of this wire in block-local coordinates.
|
||||
/// `[min_x, min_y, max_x, max_y]`. Used for view-frustum culling at
|
||||
/// expand-time: transform corners by the Insert transform → world AABB
|
||||
@@ -89,6 +96,18 @@ pub struct NestedRef {
|
||||
pub color_is_byblock: bool,
|
||||
pub lt_is_byblock: bool,
|
||||
pub lw_is_byblock: bool,
|
||||
/// The nested INSERT's own properties are ByLayer. Combined with
|
||||
/// `layer_is_zero` these drive the layer-0 rule for the nested insert
|
||||
/// *itself* (so its ByBlock leaves inherit the outer layer, not layer 0).
|
||||
pub color_is_bylayer: bool,
|
||||
pub lt_is_bylayer: bool,
|
||||
pub lw_is_bylayer: bool,
|
||||
/// The nested INSERT itself sits on layer "0": its own layer-0 children
|
||||
/// chain up to the outer insert's layer rather than resolving to `l0`.
|
||||
pub layer_is_zero: bool,
|
||||
/// The nested INSERT's own layer style — the layer-0 inheritance target
|
||||
/// for its children when the nested insert is not itself on layer 0.
|
||||
pub l0: crate::scene::view::render::InheritStyle,
|
||||
pub instance_offsets: Vec<[f64; 3]>,
|
||||
/// XCLIP boundary for this nested insert, in the parent defn's local frame
|
||||
/// (`None` = unclipped). Baked at build time because the clip's spatial
|
||||
@@ -322,6 +341,9 @@ fn build_nested_ref(
|
||||
// against different backgrounds without rebuilding.
|
||||
let (ins_color, ins_pat_len, ins_pat, ins_lw_px, _) =
|
||||
crate::scene::view::render::render_style_for(doc, &EntityType::Insert(nested_ins.clone()));
|
||||
// The nested insert's own layer style — the layer-0 target for its
|
||||
// children (raw colour; adapted at emit like `ins_color`).
|
||||
let l0 = crate::scene::view::render::layer_render_style(doc, &nested_ins.common.layer);
|
||||
let _ = bg_color;
|
||||
|
||||
// Bake the XCLIP boundary (parent-defn-local) so the nested insert keeps
|
||||
@@ -340,6 +362,17 @@ fn build_nested_ref(
|
||||
color_is_byblock: nested_ins.common.color == AcadColor::ByBlock,
|
||||
lt_is_byblock: nested_ins.common.linetype.eq_ignore_ascii_case("byblock"),
|
||||
lw_is_byblock: matches!(nested_ins.common.line_weight, LineWeight::ByBlock),
|
||||
color_is_bylayer: nested_ins.common.color == AcadColor::ByLayer,
|
||||
lt_is_bylayer: {
|
||||
let lt = &nested_ins.common.linetype;
|
||||
lt.is_empty() || lt.eq_ignore_ascii_case("bylayer")
|
||||
},
|
||||
lw_is_bylayer: matches!(
|
||||
nested_ins.common.line_weight,
|
||||
LineWeight::ByLayer | LineWeight::Default
|
||||
),
|
||||
layer_is_zero: nested_ins.common.layer == "0",
|
||||
l0,
|
||||
instance_offsets: array_offsets(nested_ins),
|
||||
clip_poly,
|
||||
}
|
||||
@@ -372,6 +405,21 @@ fn tessellate_sub_local(
|
||||
let lt_is_byblock = sub.common().linetype.eq_ignore_ascii_case("byblock");
|
||||
let lw_is_byblock = matches!(sub.common().line_weight, LineWeight::ByBlock);
|
||||
|
||||
// Layer-0 rule: a child on layer "0" with ByLayer properties inherits the
|
||||
// INSERT's layer at expand time. Flag each ByLayer property so emit_wire
|
||||
// can override the cached (layer-0-resolved) value with the insert layer's.
|
||||
let on_l0 = sub.common().layer == "0";
|
||||
let color_l0 = on_l0 && sub.common().color == AcadColor::ByLayer;
|
||||
let lt_l0 = on_l0 && {
|
||||
let lt = &sub.common().linetype;
|
||||
lt.is_empty() || lt.eq_ignore_ascii_case("bylayer")
|
||||
};
|
||||
let lw_l0 = on_l0
|
||||
&& matches!(
|
||||
sub.common().line_weight,
|
||||
LineWeight::ByLayer | LineWeight::Default
|
||||
);
|
||||
|
||||
// Pass `local_offset` as the f64 world-offset so tessellate subtracts it
|
||||
// before casting to f32 — same precision-preservation trick used for
|
||||
// top-level entities, applied per-defn.
|
||||
@@ -437,6 +485,9 @@ fn tessellate_sub_local(
|
||||
color_is_byblock,
|
||||
lt_is_byblock,
|
||||
lw_is_byblock,
|
||||
color_l0,
|
||||
lt_l0,
|
||||
lw_l0,
|
||||
aabb_local,
|
||||
text_height_local,
|
||||
text_obb_local,
|
||||
@@ -537,6 +588,8 @@ pub fn expand_insert(
|
||||
ins_pat_len: f32,
|
||||
ins_pat: [f32; 8],
|
||||
ins_lw_px: f32,
|
||||
// The INSERT's own layer style — layer-0 inheritance target for children.
|
||||
ins_layer: crate::scene::view::render::InheritStyle,
|
||||
selected: bool,
|
||||
pslt_factor: f32,
|
||||
// World-space XY view AABB (with world_offset already subtracted, so the
|
||||
@@ -598,6 +651,7 @@ pub fn expand_insert(
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
l0: ins_layer,
|
||||
selected,
|
||||
pslt_factor,
|
||||
view_aabb,
|
||||
@@ -693,18 +747,7 @@ fn emit_greeked_text(
|
||||
return;
|
||||
}
|
||||
|
||||
let final_color = if ctx.selected {
|
||||
WireModel::SELECTED
|
||||
} else if lw.color_is_byblock {
|
||||
ctx.ins_color
|
||||
} else {
|
||||
lw.color
|
||||
};
|
||||
let final_color = if ctx.is_xref && !ctx.selected {
|
||||
fade_toward_bg(final_color, ctx.bg_color)
|
||||
} else {
|
||||
final_color
|
||||
};
|
||||
let final_color = resolve_wire_color(lw, ctx);
|
||||
|
||||
let key = style_key(final_color, 0.0, [0.0; 8], 1.0, lw.aci, true, true);
|
||||
let entry = out
|
||||
@@ -790,18 +833,7 @@ fn emit_text_baseline(
|
||||
(ux / ulen, uy / ulen, uz / ulen)
|
||||
};
|
||||
|
||||
let final_color = if ctx.selected {
|
||||
WireModel::SELECTED
|
||||
} else if lw.color_is_byblock {
|
||||
ctx.ins_color
|
||||
} else {
|
||||
lw.color
|
||||
};
|
||||
let final_color = if ctx.is_xref && !ctx.selected {
|
||||
fade_toward_bg(final_color, ctx.bg_color)
|
||||
} else {
|
||||
final_color
|
||||
};
|
||||
let final_color = resolve_wire_color(lw, ctx);
|
||||
|
||||
let key = style_key(final_color, 0.0, [0.0; 8], 1.0, lw.aci, true, false);
|
||||
let entry = out
|
||||
@@ -868,6 +900,9 @@ struct ExpandCtx<'a> {
|
||||
ins_pat_len: f32,
|
||||
ins_pat: [f32; 8],
|
||||
ins_lw_px: f32,
|
||||
/// Layer-0 inheritance target — the current INSERT's *layer* style, used
|
||||
/// for child wires on layer "0" whose properties are ByLayer.
|
||||
l0: crate::scene::view::render::InheritStyle,
|
||||
selected: bool,
|
||||
pslt_factor: f32,
|
||||
// World-space XY view AABB (post world_offset). `None` = no culling.
|
||||
@@ -1202,28 +1237,43 @@ fn expand_defn(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Resolve ByBlock for this nested ref against the outer ctx.
|
||||
// Resolve the nested insert's own style against the outer ctx:
|
||||
// ByBlock inherits the outer insert; a nested insert that is
|
||||
// itself on layer "0" with ByLayer props inherits the outer
|
||||
// layer-0 target (so its ByBlock leaves resolve to that layer,
|
||||
// not layer 0). Mirrors the leaf resolution in emit_wire.
|
||||
let nested_color = if nref.color_is_byblock {
|
||||
ctx.ins_color
|
||||
} else if nref.layer_is_zero && nref.color_is_bylayer {
|
||||
ctx.l0.color
|
||||
} else {
|
||||
nref.ins_color
|
||||
};
|
||||
let (nested_pat_len, nested_pat) = if nref.lt_is_byblock {
|
||||
(ctx.ins_pat_len, ctx.ins_pat)
|
||||
} else if nref.layer_is_zero && nref.lt_is_bylayer {
|
||||
(ctx.l0.pat_len, ctx.l0.pat)
|
||||
} else {
|
||||
(nref.ins_pat_len, nref.ins_pat)
|
||||
};
|
||||
let nested_lw_px = if nref.lw_is_byblock {
|
||||
ctx.ins_lw_px
|
||||
} else if nref.layer_is_zero && nref.lw_is_bylayer {
|
||||
ctx.l0.lw_px
|
||||
} else {
|
||||
nref.ins_lw_px
|
||||
};
|
||||
// Layer-0 target for the nested expansion: a nested insert that
|
||||
// is itself on layer 0 chains up to the outer target; otherwise
|
||||
// its layer-0 children resolve to the nested insert's own layer.
|
||||
let nested_l0 = if nref.layer_is_zero { ctx.l0 } else { nref.l0 };
|
||||
let inner_ctx = ExpandCtx {
|
||||
cache: ctx.cache,
|
||||
ins_color: nested_color,
|
||||
ins_pat_len: nested_pat_len,
|
||||
ins_pat: nested_pat,
|
||||
ins_lw_px: nested_lw_px,
|
||||
l0: nested_l0,
|
||||
selected: ctx.selected,
|
||||
pslt_factor: ctx.pslt_factor,
|
||||
view_aabb: ctx.view_aabb,
|
||||
@@ -1279,6 +1329,28 @@ fn expand_defn(
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a cached LocalWire's final colour against the current expansion
|
||||
/// context: selection override first, then ByBlock → insert colour, then the
|
||||
/// 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 {
|
||||
WireModel::SELECTED
|
||||
} else if lw.color_is_byblock {
|
||||
ctx.ins_color
|
||||
} else if lw.color_l0 {
|
||||
// Inherit the insert layer's RGB but keep the child's own transparency.
|
||||
[ctx.l0.color[0], ctx.l0.color[1], ctx.l0.color[2], lw.color[3]]
|
||||
} else {
|
||||
lw.color
|
||||
};
|
||||
if ctx.is_xref && !ctx.selected {
|
||||
fade_toward_bg(c, ctx.bg_color)
|
||||
} else {
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_wire(
|
||||
lw: &LocalWire,
|
||||
accum_xform: &Transform,
|
||||
@@ -1291,25 +1363,18 @@ fn emit_wire(
|
||||
|
||||
// Resolve final style for this LocalWire against the outer Insert ctx
|
||||
// before we hash it into a batch.
|
||||
let final_color = if ctx.selected {
|
||||
WireModel::SELECTED
|
||||
} else if lw.color_is_byblock {
|
||||
ctx.ins_color
|
||||
} else {
|
||||
lw.color
|
||||
};
|
||||
let final_color = if ctx.is_xref && !ctx.selected {
|
||||
fade_toward_bg(final_color, ctx.bg_color)
|
||||
} else {
|
||||
final_color
|
||||
};
|
||||
let final_color = resolve_wire_color(lw, ctx);
|
||||
let (final_pat_len, final_pat) = if lw.lt_is_byblock {
|
||||
(ctx.ins_pat_len, ctx.ins_pat)
|
||||
} else if lw.lt_l0 {
|
||||
(ctx.l0.pat_len, ctx.l0.pat)
|
||||
} else {
|
||||
(lw.pattern_length, lw.pattern)
|
||||
};
|
||||
let final_lw_px = if lw.lw_is_byblock {
|
||||
ctx.ins_lw_px
|
||||
} else if lw.lw_l0 {
|
||||
ctx.l0.lw_px
|
||||
} else {
|
||||
lw.line_weight_px
|
||||
};
|
||||
|
||||
@@ -220,6 +220,20 @@ pub(crate) fn tessellate_entity(
|
||||
if !br.entity_handles.is_empty() {
|
||||
let mut wires: Vec<WireModel> =
|
||||
Vec::with_capacity(br.entity_handles.len());
|
||||
// The Dimension's own layer style — layer-0 inheritance
|
||||
// target for baked sub-entities on layer "0" (#221).
|
||||
let dim_l0_color = view::render::adapt_to_bg(
|
||||
view::render::layer_render_style(document, &e.common().layer).color,
|
||||
bg_color,
|
||||
);
|
||||
let dim_l0_aci = document
|
||||
.layers
|
||||
.get(&e.common().layer)
|
||||
.map(|l| match &l.color {
|
||||
acadrust::types::Color::Index(i) => *i,
|
||||
_ => 0,
|
||||
})
|
||||
.unwrap_or(0);
|
||||
for &eh in &br.entity_handles {
|
||||
let Some(sub) = document.get_entity(eh) else { continue };
|
||||
// Sub-entities inside *D### / DIMBLOCK## blocks
|
||||
@@ -227,6 +241,8 @@ pub(crate) fn tessellate_entity(
|
||||
// they should inherit from the Dimension entity.
|
||||
let sub_color_is_byblock =
|
||||
sub.common().color == acadrust::types::Color::ByBlock;
|
||||
let sub_is_l0_bylayer = sub.common().layer == "0"
|
||||
&& sub.common().color == acadrust::types::Color::ByLayer;
|
||||
let sub_wires = tessellate_entity(
|
||||
document, selected, active_viewport, bg_color,
|
||||
// Block contents are baked at the final WCS size —
|
||||
@@ -238,10 +254,14 @@ pub(crate) fn tessellate_entity(
|
||||
// Override ByBlock colour with the dim's resolved
|
||||
// colour so text matches `DIMCLRT`-style behaviour
|
||||
// (or layer colour) instead of the raw ByBlock
|
||||
// fallback that render_style_for produces.
|
||||
// fallback that render_style_for produces. A layer-0
|
||||
// sub inherits the dim's layer colour instead.
|
||||
if sub_color_is_byblock {
|
||||
w.color = if sel { WireModel::SELECTED } else { entity_color };
|
||||
w.aci = aci;
|
||||
} else if sub_is_l0_bylayer && !sel {
|
||||
w.color = dim_l0_color;
|
||||
w.aci = dim_l0_aci;
|
||||
}
|
||||
wires.push(w);
|
||||
}
|
||||
@@ -320,10 +340,26 @@ pub(crate) fn tessellate_entity(
|
||||
if !br.entity_handles.is_empty() {
|
||||
let mut wires: Vec<WireModel> =
|
||||
Vec::with_capacity(br.entity_handles.len());
|
||||
// The Table's own layer style — layer-0 inheritance target
|
||||
// for baked sub-entities on layer "0" (#221).
|
||||
let tab_l0_color = view::render::adapt_to_bg(
|
||||
view::render::layer_render_style(document, &e.common().layer).color,
|
||||
bg_color,
|
||||
);
|
||||
let tab_l0_aci = document
|
||||
.layers
|
||||
.get(&e.common().layer)
|
||||
.map(|l| match &l.color {
|
||||
acadrust::types::Color::Index(i) => *i,
|
||||
_ => 0,
|
||||
})
|
||||
.unwrap_or(0);
|
||||
for &eh in &br.entity_handles {
|
||||
let Some(sub) = document.get_entity(eh) else { continue };
|
||||
let sub_color_is_byblock =
|
||||
sub.common().color == acadrust::types::Color::ByBlock;
|
||||
let sub_is_l0_bylayer = sub.common().layer == "0"
|
||||
&& sub.common().color == acadrust::types::Color::ByLayer;
|
||||
let sub_wires = tessellate_entity(
|
||||
document, selected, active_viewport, bg_color,
|
||||
anno_scale, sub, block_cache, view_aabb, world_per_pixel,
|
||||
@@ -333,6 +369,9 @@ pub(crate) fn tessellate_entity(
|
||||
if sub_color_is_byblock {
|
||||
w.color = if sel { WireModel::SELECTED } else { entity_color };
|
||||
w.aci = aci;
|
||||
} else if sub_is_l0_bylayer && !sel {
|
||||
w.color = tab_l0_color;
|
||||
w.aci = tab_l0_aci;
|
||||
}
|
||||
wires.push(w);
|
||||
}
|
||||
@@ -367,6 +406,13 @@ pub(crate) fn tessellate_entity(
|
||||
// Resolve the INSERT's own style so ByBlock sub-entities can inherit it.
|
||||
let (ins_color, ins_pat_len, ins_pat, ins_lw_px, _) = view::render::render_style_for(document, e);
|
||||
let ins_color = view::render::adapt_to_bg(ins_color, bg_color);
|
||||
// Resolve the INSERT's *layer* style — the layer-0 inheritance target
|
||||
// for sub-entities on layer "0" with ByLayer properties (#221).
|
||||
let ins_layer = {
|
||||
let mut s = view::render::layer_render_style(document, &ins.common.layer);
|
||||
s.color = view::render::adapt_to_bg(s.color, bg_color);
|
||||
s
|
||||
};
|
||||
let ip = glam::Vec3::new(
|
||||
(ins.insert_point.x) as f32,
|
||||
(ins.insert_point.y) as f32,
|
||||
@@ -408,6 +454,7 @@ pub(crate) fn tessellate_entity(
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
sel,
|
||||
pslt_factor,
|
||||
view_aabb,
|
||||
@@ -438,6 +485,7 @@ pub(crate) fn tessellate_entity(
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
bg_color,
|
||||
is_xref,
|
||||
pslt_factor,
|
||||
@@ -469,6 +517,7 @@ pub(crate) fn tessellate_entity(
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
);
|
||||
let sub_color = view::render::adapt_to_bg(sub_color, bg_color);
|
||||
let sub_color = if is_xref && !sel {
|
||||
@@ -509,6 +558,7 @@ pub(crate) fn tessellate_entity(
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
bg_color,
|
||||
is_xref,
|
||||
pslt_factor,
|
||||
|
||||
+66
-5
@@ -377,6 +377,12 @@ impl Scene {
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Background for adapting block-child hatch colours at the leaf (#221).
|
||||
let hatch_bg: [f32; 4] = if self.current_layout != "Model" {
|
||||
self.paper_bg_color
|
||||
} else {
|
||||
self.bg_color
|
||||
};
|
||||
for entity in self.document.entities() {
|
||||
let EntityType::Insert(ins) = entity else {
|
||||
continue;
|
||||
@@ -402,26 +408,81 @@ impl Scene {
|
||||
// hatches land in the correct world position. A depth guard keeps
|
||||
// a malformed cyclic block reference from looping forever.
|
||||
let normalize = crate::modules::draw::modify::explode::normalize_insert_entity;
|
||||
let mut stack: Vec<(EntityType, usize)> = ins
|
||||
// Block-child colour inheritance sources (#221), kept RAW and
|
||||
// adapted at the leaf. `ins_color` feeds ByBlock; `l0` (the
|
||||
// INSERT's *layer* style) feeds the layer-0 rule. Both chain
|
||||
// through nested inserts, mirroring expand_insert.
|
||||
let ins_color =
|
||||
crate::scene::view::render::render_style_for(&self.document, entity).0;
|
||||
let l0 = crate::scene::view::render::layer_render_style(
|
||||
&self.document,
|
||||
&ins.common.layer,
|
||||
);
|
||||
let mut stack: Vec<(
|
||||
EntityType,
|
||||
usize,
|
||||
[f32; 4],
|
||||
crate::scene::view::render::InheritStyle,
|
||||
)> = ins
|
||||
.explode_from_document(&self.document)
|
||||
.into_iter()
|
||||
.map(|e| (normalize(e), 0usize))
|
||||
.map(|e| (normalize(e), 0usize, ins_color, l0))
|
||||
.collect();
|
||||
while let Some((sub, depth)) = stack.pop() {
|
||||
while let Some((sub, depth, sub_ins_color, sub_l0)) = stack.pop() {
|
||||
match sub {
|
||||
EntityType::Insert(nins) => {
|
||||
if depth >= 32 {
|
||||
continue;
|
||||
}
|
||||
use acadrust::types::Color;
|
||||
// Chain the inheritance sources into the nested insert,
|
||||
// mirroring expand_insert's nested resolution: ByBlock →
|
||||
// parent source; layer-0 + ByLayer → parent layer-0
|
||||
// target; else the nested insert's own resolved style.
|
||||
let child_ins_color = if nins.common.color == Color::ByBlock {
|
||||
sub_ins_color
|
||||
} else if nins.common.layer == "0"
|
||||
&& nins.common.color == Color::ByLayer
|
||||
{
|
||||
sub_l0.color
|
||||
} else {
|
||||
crate::scene::view::render::render_style_for(
|
||||
&self.document,
|
||||
&EntityType::Insert(nins.clone()),
|
||||
)
|
||||
.0
|
||||
};
|
||||
let child_l0 = if nins.common.layer == "0" {
|
||||
sub_l0
|
||||
} else {
|
||||
crate::scene::view::render::layer_render_style(
|
||||
&self.document,
|
||||
&nins.common.layer,
|
||||
)
|
||||
};
|
||||
for e in nins.explode_from_document(&self.document) {
|
||||
stack.push((normalize(e), depth + 1));
|
||||
stack.push((normalize(e), depth + 1, child_ins_color, child_l0));
|
||||
}
|
||||
}
|
||||
EntityType::Hatch(dxf) => {
|
||||
if dxf.common.invisible || layer_hidden(&dxf.common.layer) {
|
||||
continue;
|
||||
}
|
||||
let color = self.render_style(&EntityType::Hatch(dxf.clone())).0;
|
||||
// Resolve ByBlock / layer-0 inheritance for this block
|
||||
// child, then adapt to the background (#221). Pattern /
|
||||
// lineweight args are unused by a hatch's colour.
|
||||
let color = crate::scene::view::render::render_style_for_block_sub(
|
||||
&self.document,
|
||||
&EntityType::Hatch(dxf.clone()),
|
||||
sub_ins_color,
|
||||
0.0,
|
||||
[0.0; 8],
|
||||
0.0,
|
||||
sub_l0,
|
||||
)
|
||||
.0;
|
||||
let color =
|
||||
crate::scene::view::render::adapt_to_bg(color, hatch_bg);
|
||||
if let Some(mut model) =
|
||||
Self::hatch_model_from_dxf(&dxf, color)
|
||||
{
|
||||
|
||||
+121
-6
@@ -2321,8 +2321,28 @@ impl Scene {
|
||||
if !self.mesh_entity_visible(ins.common.handle) {
|
||||
continue;
|
||||
}
|
||||
// Colour inheritance sources for block-internal solids (#221).
|
||||
let bg = self.current_bg();
|
||||
let ins_color = crate::scene::view::render::adapt_to_bg(
|
||||
crate::scene::view::render::render_style_for(&self.document, e).0,
|
||||
bg,
|
||||
);
|
||||
let l0 = crate::scene::view::render::adapt_to_bg(
|
||||
crate::scene::view::render::layer_render_style(
|
||||
&self.document,
|
||||
&ins.common.layer,
|
||||
)
|
||||
.color,
|
||||
bg,
|
||||
);
|
||||
let start = out.len();
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, &mut out);
|
||||
self.expand_block_meshes(
|
||||
&ins.block_name,
|
||||
&ins.get_transform(),
|
||||
0,
|
||||
Some((ins_color, l0)),
|
||||
&mut out,
|
||||
);
|
||||
// Tag the instanced meshes with the parent INSERT handle so the
|
||||
// hover / selection highlight (keyed on the mesh name) tints the
|
||||
// block, not the inner solid's own handle which nothing selects.
|
||||
@@ -2344,6 +2364,11 @@ impl Scene {
|
||||
block_name: &str,
|
||||
accum: &acadrust::types::Transform,
|
||||
depth: usize,
|
||||
// Block-child colour inheritance sources, bg-adapted:
|
||||
// `(insert_color, insert_layer_color)`. `Some` only on the render path;
|
||||
// pick paths pass `None` (colour irrelevant). Drives the ByBlock /
|
||||
// layer-0 overrides for block-internal solids (#221).
|
||||
inherit: Option<([f32; 4], [f32; 4])>,
|
||||
out: &mut Vec<MeshLodSet>,
|
||||
) {
|
||||
if depth > 16 {
|
||||
@@ -2364,13 +2389,103 @@ impl Scene {
|
||||
}
|
||||
if let EntityType::Insert(ins) = e {
|
||||
let composed = ins.get_transform().then(accum);
|
||||
self.expand_block_meshes(&ins.block_name, &composed, depth + 1, out);
|
||||
let child = inherit.map(|(pc, pl0)| self.chain_mesh_inherit(ins, pc, pl0));
|
||||
self.expand_block_meshes(&ins.block_name, &composed, depth + 1, child, out);
|
||||
} else if let Some(set) = self.block_meshes.get(&h) {
|
||||
out.push(transform_block_mesh_lod_set(set, accum));
|
||||
// The solid's own transparency (baked into the cached colour).
|
||||
let own_alpha = set.lods.first().map(|m| m.color[3]).unwrap_or(1.0);
|
||||
let mut ts = transform_block_mesh_lod_set(set, accum);
|
||||
// Re-resolve colour against the INSERT context: a block-internal
|
||||
// solid that is ByBlock or on layer "0" + ByLayer can't be
|
||||
// coloured at cache-build time (no insert context there). (#221)
|
||||
if let Some(c) = self.block_mesh_override_color(e, h, inherit, own_alpha) {
|
||||
for lod in &mut ts.lods {
|
||||
lod.color = c;
|
||||
}
|
||||
}
|
||||
out.push(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background colour for the current layout (model vs paper).
|
||||
fn current_bg(&self) -> [f32; 4] {
|
||||
if self.current_layout != "Model" {
|
||||
self.paper_bg_color
|
||||
} else {
|
||||
self.bg_color
|
||||
}
|
||||
}
|
||||
|
||||
/// Chain block-child colour inheritance into a nested INSERT, mirroring
|
||||
/// `expand_insert`: ByBlock keeps the parent source; a nested insert on
|
||||
/// layer "0" with ByLayer colour adopts the parent layer-0 target; else it
|
||||
/// uses its own resolved colour / layer. Returned colours are bg-adapted.
|
||||
fn chain_mesh_inherit(
|
||||
&self,
|
||||
ins: &acadrust::entities::Insert,
|
||||
parent_ins_color: [f32; 4],
|
||||
parent_l0: [f32; 4],
|
||||
) -> ([f32; 4], [f32; 4]) {
|
||||
use acadrust::types::Color;
|
||||
let bg = self.current_bg();
|
||||
let child_ins_color = if ins.common.color == Color::ByBlock {
|
||||
parent_ins_color
|
||||
} else if ins.common.layer == "0" && ins.common.color == Color::ByLayer {
|
||||
parent_l0
|
||||
} else {
|
||||
crate::scene::view::render::adapt_to_bg(
|
||||
crate::scene::view::render::render_style_for(
|
||||
&self.document,
|
||||
&EntityType::Insert(ins.clone()),
|
||||
)
|
||||
.0,
|
||||
bg,
|
||||
)
|
||||
};
|
||||
let child_l0 = if ins.common.layer == "0" {
|
||||
parent_l0
|
||||
} else {
|
||||
crate::scene::view::render::adapt_to_bg(
|
||||
crate::scene::view::render::layer_render_style(&self.document, &ins.common.layer)
|
||||
.color,
|
||||
bg,
|
||||
)
|
||||
};
|
||||
(child_ins_color, child_l0)
|
||||
}
|
||||
|
||||
/// Per-instance colour override for a block-internal solid mesh: ByBlock →
|
||||
/// insert colour, layer-0 + ByLayer → insert-layer colour, else `None`
|
||||
/// (keep the cached own-layer colour). Applies the same REFEDIT fade as
|
||||
/// `recolor_meshes`, keyed on the inner solid handle. (#221)
|
||||
fn block_mesh_override_color(
|
||||
&self,
|
||||
e: &EntityType,
|
||||
h: Handle,
|
||||
inherit: Option<([f32; 4], [f32; 4])>,
|
||||
own_alpha: f32,
|
||||
) -> Option<[f32; 4]> {
|
||||
let (ins_color, l0_color) = inherit?;
|
||||
use acadrust::types::Color;
|
||||
let common = e.common();
|
||||
let mut c = if common.color == Color::ByBlock {
|
||||
ins_color
|
||||
} else if common.layer == "0" && common.color == Color::ByLayer {
|
||||
// Inherit the insert layer's RGB but keep the solid's own alpha,
|
||||
// matching the wire/hatch path (render_style_for_block_sub).
|
||||
[l0_color[0], l0_color[1], l0_color[2], own_alpha]
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if let Some(keep) = &self.refedit_keep {
|
||||
if !keep.contains(&h) {
|
||||
c = crate::scene::cache::block_cache::fade_toward_bg(c, self.current_bg());
|
||||
}
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
|
||||
/// Hatches eligible for click / box / lasso hit-testing in the current
|
||||
/// layout. Filters out block-internal source hatches (stored in
|
||||
/// `self.hatches` at block-local coords for the block-defn position,
|
||||
@@ -2572,7 +2687,7 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
let mut sets = Vec::new();
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, &mut sets);
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, None, &mut sets);
|
||||
for set in sets {
|
||||
if let Some(m) = set.lods.into_iter().next() {
|
||||
block_owned.push((ins.common.handle, m));
|
||||
@@ -2615,7 +2730,7 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
let mut sets = Vec::new();
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, &mut sets);
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, None, &mut sets);
|
||||
let hit = sets.iter().any(|set| {
|
||||
set.lods.first().map_or(false, |m| {
|
||||
!pick::hit_test::mesh_box_hit(
|
||||
@@ -2660,7 +2775,7 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
let mut sets = Vec::new();
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, &mut sets);
|
||||
self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, None, &mut sets);
|
||||
let hit = sets.iter().any(|set| {
|
||||
set.lods.first().map_or(false, |m| {
|
||||
!pick::hit_test::mesh_poly_hit(
|
||||
|
||||
+155
-15
@@ -533,26 +533,65 @@ pub(in crate::scene) fn render_style_for(
|
||||
.unwrap_or(&LineWeight::Default),
|
||||
_ => ew,
|
||||
};
|
||||
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;
|
||||
resolved
|
||||
.millimeters()
|
||||
.map(|mm| (mm as f32 * MM_TO_PX * LWT_DISPLAY_BOOST).max(1.0))
|
||||
.unwrap_or(1.0)
|
||||
lineweight_to_px(resolved)
|
||||
};
|
||||
|
||||
(entity_color, pattern_length, pattern, line_weight_px, aci)
|
||||
}
|
||||
|
||||
/// Like `render_style_for` but resolves ByBlock properties by inheriting from
|
||||
/// the INSERT entity's already-resolved style. Call this for exploded block
|
||||
/// sub-entities so that ByBlock color/linetype/lineweight propagate correctly.
|
||||
/// Resolved render style used as the inheritance source for a block child's
|
||||
/// ByBlock properties (the INSERT's own style) or its layer-0 properties (the
|
||||
/// INSERT's *layer* style). Bundled so it threads through the block-expansion
|
||||
/// call chain as a single value.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct InheritStyle {
|
||||
pub color: [f32; 4],
|
||||
pub pat_len: f32,
|
||||
pub pat: [f32; 8],
|
||||
pub lw_px: f32,
|
||||
}
|
||||
|
||||
/// 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))
|
||||
.unwrap_or(1.0)
|
||||
}
|
||||
|
||||
/// Resolve a layer's own color / linetype / lineweight to concrete render
|
||||
/// values — what a fully-ByLayer entity on that layer would draw as. Used for
|
||||
/// the layer-0 block rule: a block child on layer "0" inherits the block
|
||||
/// reference's layer through this. Color is returned RAW (background adaptation
|
||||
/// happens at emit time). Falls back to white / Continuous / 1 px when the
|
||||
/// layer is missing.
|
||||
pub(crate) fn layer_render_style(document: &CadDocument, layer_name: &str) -> InheritStyle {
|
||||
let layer = document.layers.get(layer_name);
|
||||
let color = layer.map(|l| &l.color).unwrap_or(&AcadColor::WHITE);
|
||||
let [r, g, b, _] = tess_util::aci_to_rgba(color);
|
||||
let lt_name = layer.map(|l| l.line_type.as_str()).unwrap_or("Continuous");
|
||||
let lt_scale = document.header.linetype_scale as f32;
|
||||
let (pat_len, pat) = resolve_pattern(&document.line_types, lt_name, lt_scale);
|
||||
let lw = layer.map(|l| &l.line_weight).unwrap_or(&LineWeight::Default);
|
||||
InheritStyle {
|
||||
color: [r, g, b, 1.0],
|
||||
pat_len,
|
||||
pat,
|
||||
lw_px: lineweight_to_px(lw),
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `render_style_for` but resolves a block sub-entity's inherited
|
||||
/// properties: ByBlock inherits the INSERT's style, and (the layer-0 rule) a
|
||||
/// sub-entity on layer "0" with ByLayer properties inherits the INSERT's
|
||||
/// *layer* style (`l0`). Explicit properties always win. Call this for
|
||||
/// exploded block sub-entities so color/linetype/lineweight propagate right.
|
||||
pub(crate) fn render_style_for_block_sub(
|
||||
document: &CadDocument,
|
||||
e: &EntityType,
|
||||
@@ -560,24 +599,35 @@ pub(crate) fn render_style_for_block_sub(
|
||||
insert_pat_len: f32,
|
||||
insert_pat: [f32; 8],
|
||||
insert_lw_px: f32,
|
||||
l0: InheritStyle,
|
||||
) -> ([f32; 4], f32, [f32; 8], f32, u8) {
|
||||
let (color, pat_len, pat, lw_px, aci) = render_style_for(document, e);
|
||||
let common = e.common();
|
||||
let on_l0 = common.layer == "0";
|
||||
|
||||
let final_color = if common.color == AcadColor::ByBlock {
|
||||
insert_color
|
||||
} else if on_l0 && common.color == AcadColor::ByLayer {
|
||||
// Inherit the insert layer's RGB but keep the child's own transparency.
|
||||
[l0.color[0], l0.color[1], l0.color[2], color[3]]
|
||||
} else {
|
||||
color
|
||||
};
|
||||
|
||||
let lt_bylayer =
|
||||
common.linetype.is_empty() || common.linetype.eq_ignore_ascii_case("bylayer");
|
||||
let (final_pat_len, final_pat) = if common.linetype.eq_ignore_ascii_case("byblock") {
|
||||
(insert_pat_len, insert_pat)
|
||||
} else if on_l0 && lt_bylayer {
|
||||
(l0.pat_len, l0.pat)
|
||||
} else {
|
||||
(pat_len, pat)
|
||||
};
|
||||
|
||||
let final_lw = if matches!(common.line_weight, LineWeight::ByBlock) {
|
||||
insert_lw_px
|
||||
} else if on_l0 && matches!(common.line_weight, LineWeight::ByLayer | LineWeight::Default) {
|
||||
l0.lw_px
|
||||
} else {
|
||||
lw_px
|
||||
};
|
||||
@@ -1013,3 +1063,93 @@ fn split_face3d_wires(
|
||||
}
|
||||
(face3d, others)
|
||||
}
|
||||
|
||||
// ── Layer-0 block inheritance (#221) ──────────────────────────────────────
|
||||
// A block child on layer "0" with ByLayer properties inherits the block
|
||||
// reference's *layer*; every other layer is "sticky" (keeps its own layer);
|
||||
// ByBlock inherits the insert's own style; explicit properties always win.
|
||||
#[cfg(test)]
|
||||
mod layer0_inherit_tests {
|
||||
use super::*;
|
||||
use acadrust::entities::Line;
|
||||
use acadrust::tables::Layer;
|
||||
use acadrust::types::{Color, Transparency};
|
||||
|
||||
// ACI: 1 = red, 3 = green, 7 = white. Distinct, so the assertions below
|
||||
// can tell "inherited the insert layer" from "kept layer 0".
|
||||
fn doc() -> CadDocument {
|
||||
let mut d = CadDocument::new();
|
||||
let mut walls = Layer::new("Walls");
|
||||
walls.color = Color::Index(1); // red
|
||||
d.layers.add_or_replace(walls);
|
||||
let mut zero = Layer::new("0");
|
||||
zero.color = Color::Index(7); // white
|
||||
d.layers.add_or_replace(zero);
|
||||
let mut other = Layer::new("Other");
|
||||
other.color = Color::Index(3); // green
|
||||
d.layers.add_or_replace(other);
|
||||
d
|
||||
}
|
||||
|
||||
fn child(layer: &str, color: Color) -> EntityType {
|
||||
let mut l = Line::new();
|
||||
l.common.layer = layer.to_string();
|
||||
l.common.color = color;
|
||||
EntityType::Line(l)
|
||||
}
|
||||
|
||||
fn resolve(d: &CadDocument, e: &EntityType, ins: [f32; 4]) -> [f32; 4] {
|
||||
// Insert sits on "Walls"; its layer style is the layer-0 target.
|
||||
let l0 = layer_render_style(d, "Walls");
|
||||
render_style_for_block_sub(d, e, ins, l0.pat_len, l0.pat, l0.lw_px, l0).0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer0_bylayer_inherits_insert_layer() {
|
||||
let d = doc();
|
||||
let walls = layer_render_style(&d, "Walls").color;
|
||||
let zero = layer_render_style(&d, "0").color;
|
||||
let c = resolve(&d, &child("0", Color::ByLayer), walls);
|
||||
assert_eq!(&c[..3], &walls[..3], "layer-0 child must show the insert's layer (Walls)");
|
||||
assert_ne!(&c[..3], &zero[..3], "layer-0 child must NOT show layer 0's own color");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonzero_layer_is_sticky() {
|
||||
let d = doc();
|
||||
let walls = layer_render_style(&d, "Walls").color;
|
||||
let other = layer_render_style(&d, "Other").color;
|
||||
let c = resolve(&d, &child("Other", Color::ByLayer), walls);
|
||||
assert_eq!(&c[..3], &other[..3], "a child on a normal layer keeps its own layer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byblock_inherits_insert_color() {
|
||||
let d = doc();
|
||||
let ins = [0.2, 0.4, 0.6, 1.0];
|
||||
let c = resolve(&d, &child("0", Color::ByBlock), ins);
|
||||
assert_eq!(&c[..3], &ins[..3], "ByBlock child uses the insert's color");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_color_wins_even_on_layer0() {
|
||||
let d = doc();
|
||||
let walls = layer_render_style(&d, "Walls").color;
|
||||
let green = tess_util::aci_to_rgba(&Color::Index(3));
|
||||
let c = resolve(&d, &child("0", Color::Index(3)), walls);
|
||||
assert_eq!(&c[..3], &green[..3], "an explicit color must win even on layer 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer0_preserves_child_transparency() {
|
||||
let d = doc();
|
||||
let walls = layer_render_style(&d, "Walls").color;
|
||||
let mut l = Line::new();
|
||||
l.common.layer = "0".to_string();
|
||||
l.common.color = Color::ByLayer;
|
||||
l.common.transparency = Transparency::from_percent(0.5); // 50% transparent
|
||||
let c = resolve(&d, &EntityType::Line(l), walls);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user