forked from 1b-agent/opencadstudio-upstream
fix(hatch): pattern-scale edits take effect + background on/off
Property edits on a hatch were invisible: fills render from the prebuilt model cache, which move/copy refreshed but the properties path never did — so a pattern-scale change (which must also rescale the stored final pattern lines by the ratio) changed nothing on screen (#415). invalidate_property_targets now rebuilds the edited fill's cached model via Scene::refresh_fill_model. The background row gains an on/off box: off removes the HATCHBACKGROUNDCOLOR record entirely (the colour could never be reverted before), on seeds ByLayer. The codec covers the full colour-method range (ByLayer / ByBlock / RGB / ACI) and the backdrop resolves ByLayer / ByBlock through the style chain. "More Colors…" on the background picker now opens the palette window targeting the background colour — it used to just close the picker. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
a7fd9ecb45
commit
9bfcad18b9
6 files changed
+154
-16
No files matched your search
@@ -831,6 +831,8 @@ pub enum ColorPickTarget {
|
||||
Table(u8, &'static str),
|
||||
/// Selected entities' colour (left properties panel).
|
||||
Properties,
|
||||
/// Selected entities' background colour (hatch / MTEXT background row).
|
||||
PropertiesBg,
|
||||
/// Current creation colour (ribbon).
|
||||
Ribbon,
|
||||
/// A layer's colour, by panel row index.
|
||||
|
||||
@@ -1087,6 +1087,10 @@ impl OpenCADStudio {
|
||||
pub(super) fn invalidate_property_targets(&mut self, i: usize, handles: &[Handle]) {
|
||||
for &handle in handles {
|
||||
self.tabs[i].scene.mark_entity_dirty(handle);
|
||||
// Hatch / SOLID fills render from prebuilt cached models; rebuild
|
||||
// them or pattern edits (scale, background, …) stay invisible
|
||||
// (#415).
|
||||
self.tabs[i].scene.refresh_fill_model(handle);
|
||||
}
|
||||
// Solid (ACIS) meshes bake their colour into the mesh, so a colour /
|
||||
// layer change needs an explicit recolour — re-tessellating wires
|
||||
|
||||
@@ -981,6 +981,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
|
||||
Some(crate::app::ColorPickTarget::Properties) => {
|
||||
Some(Message::PropColorChanged(color))
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
||||
Some(Message::PropBgColorChanged(color))
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::MText) => {
|
||||
Some(Message::MTextColorChanged(color))
|
||||
}
|
||||
|
||||
+104
-14
@@ -109,19 +109,32 @@ pub fn background_color(h: &Hatch) -> Option<acadrust::types::Color> {
|
||||
_ => None,
|
||||
})?;
|
||||
match (raw >> 24) & 0xFF {
|
||||
0xC2 | 0xC3 => Some(acadrust::types::Color::Rgb {
|
||||
// Colour-method byte: C0 = ByLayer, C1 = ByBlock, C2 = true colour,
|
||||
// C3 = ACI index.
|
||||
0xC0 => Some(acadrust::types::Color::ByLayer),
|
||||
0xC1 => Some(acadrust::types::Color::ByBlock),
|
||||
0xC2 => Some(acadrust::types::Color::Rgb {
|
||||
r: ((raw >> 16) & 0xFF) as u8,
|
||||
g: ((raw >> 8) & 0xFF) as u8,
|
||||
b: (raw & 0xFF) as u8,
|
||||
}),
|
||||
0xC3 => Some(acadrust::types::Color::Index((raw & 0xFF) as u8)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pack an RGB colour into a `HATCHBACKGROUNDCOLOR` group-1071 true-colour word.
|
||||
pub fn pack_background_color(c: &acadrust::types::Color) -> i32 {
|
||||
let (r, g, b) = c.rgb().unwrap_or((255, 255, 255));
|
||||
(0xC2000000u32 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)) as i32
|
||||
use acadrust::types::Color;
|
||||
(match c {
|
||||
Color::ByLayer => 0xC0000000u32,
|
||||
Color::ByBlock => 0xC1000000u32,
|
||||
Color::Index(i) => 0xC3000000u32 | (*i as u8 as u32),
|
||||
_ => {
|
||||
let (r, g, b) = c.rgb().unwrap_or((255, 255, 255));
|
||||
0xC2000000u32 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
|
||||
}
|
||||
}) as i32
|
||||
}
|
||||
|
||||
/// Replace (or insert) the hatch's `HATCHBACKGROUNDCOLOR` extended-data record,
|
||||
@@ -146,6 +159,25 @@ pub fn set_background_color(h: &mut Hatch, c: &acadrust::types::Color) {
|
||||
h.common.extended_data.add_record(rec);
|
||||
}
|
||||
|
||||
/// Remove the hatch's `HATCHBACKGROUNDCOLOR` extended-data record (background
|
||||
/// off), preserving every other application's records.
|
||||
pub fn clear_background_color(h: &mut Hatch) {
|
||||
use acadrust::xdata::ExtendedDataRecord;
|
||||
const APP: &str = "HATCHBACKGROUNDCOLOR";
|
||||
let kept: Vec<ExtendedDataRecord> = h
|
||||
.common
|
||||
.extended_data
|
||||
.records()
|
||||
.iter()
|
||||
.filter(|r| r.application_name != APP)
|
||||
.cloned()
|
||||
.collect();
|
||||
h.common.extended_data.clear();
|
||||
for r in kept {
|
||||
h.common.extended_data.add_record(r);
|
||||
}
|
||||
}
|
||||
|
||||
fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||
let pattern_type = match h.pattern_type {
|
||||
acadrust::entities::HatchPatternType::Predefined => "Predefined",
|
||||
@@ -162,13 +194,14 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||
let default_col = acadrust::types::Color::Index(7);
|
||||
let grad_c1 = g.colors.first().map(|e| e.color).unwrap_or(default_col);
|
||||
let grad_c2 = g.colors.get(1).map(|e| e.color).unwrap_or(default_col);
|
||||
let bg_on = background_color(h).is_some();
|
||||
let bg_col = background_color(h).unwrap_or(default_col);
|
||||
|
||||
if g.enabled {
|
||||
// ── Gradient fill ──────────────────────────────────────────────────
|
||||
let grad_type = if g.is_single_color { "One color" } else { "Two color" };
|
||||
let centered = if g.shift.abs() < 1e-9 { "Yes" } else { "No" };
|
||||
return vec![
|
||||
let mut sections = vec![
|
||||
PropSection {
|
||||
title: "Pattern".into(),
|
||||
props: vec![
|
||||
@@ -214,15 +247,29 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||
ro("Annotative", "annotative", String::new()),
|
||||
ro("Island detection style", "style", style),
|
||||
Property {
|
||||
label: "Background color".into(),
|
||||
field: "background_color",
|
||||
value: PropValue::ColorChoice(bg_col),
|
||||
},
|
||||
label: "Background".into(),
|
||||
field: "bg_enabled",
|
||||
value: PropValue::BoolToggle {
|
||||
field: "bg_enabled",
|
||||
value: bg_on,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
if bg_on {
|
||||
if let Some(sec) = sections.last_mut() {
|
||||
sec.props.push(Property {
|
||||
label: "Background color".into(),
|
||||
field: "background_color",
|
||||
value: PropValue::ColorChoice(bg_col),
|
||||
});
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
|
||||
// ── Hatch (pattern / solid) ────────────────────────────────────────────
|
||||
// "Type" = pattern definition source (Predefined / User Defined / Custom).
|
||||
let type_row = Property {
|
||||
@@ -279,7 +326,7 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||
.map(|l| (l.base_point.x, l.base_point.y))
|
||||
.unwrap_or((0.0, 0.0));
|
||||
|
||||
vec![
|
||||
let mut sections = vec![
|
||||
PropSection {
|
||||
title: "Pattern".into(),
|
||||
props: vec![
|
||||
@@ -296,9 +343,12 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||
associative_row,
|
||||
island_row,
|
||||
Property {
|
||||
label: "Background color".into(),
|
||||
field: "background_color",
|
||||
value: PropValue::ColorChoice(bg_col),
|
||||
label: "Background".into(),
|
||||
field: "bg_enabled",
|
||||
value: PropValue::BoolToggle {
|
||||
field: "bg_enabled",
|
||||
value: bg_on,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -310,7 +360,17 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||
ro("Cumulative area", "cumulative_area", format!("{:.4}", area)),
|
||||
],
|
||||
},
|
||||
]
|
||||
];
|
||||
if bg_on {
|
||||
if let Some(sec) = sections.first_mut() {
|
||||
sec.props.push(Property {
|
||||
label: "Background color".into(),
|
||||
field: "background_color",
|
||||
value: PropValue::ColorChoice(bg_col),
|
||||
});
|
||||
}
|
||||
}
|
||||
sections
|
||||
}
|
||||
|
||||
fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
|
||||
@@ -318,6 +378,17 @@ fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
|
||||
// Non-numeric fields (bool toggles / enum choices) — handled before the
|
||||
// f64 parse below, which would otherwise reject their string values.
|
||||
match field {
|
||||
// Background on/off (#415): off removes the HATCHBACKGROUNDCOLOR
|
||||
// record entirely (no background), on seeds a white one for the
|
||||
// colour picker to change.
|
||||
"bg_enabled" => {
|
||||
if background_color(h).is_some() {
|
||||
clear_background_color(h);
|
||||
} else {
|
||||
set_background_color(h, &acadrust::types::Color::ByLayer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
"double" => {
|
||||
h.is_double = if value == "toggle" {
|
||||
!h.is_double
|
||||
@@ -364,7 +435,26 @@ fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
|
||||
match field {
|
||||
"pattern_angle" if h.gradient_color.enabled => h.gradient_color.angle = v.to_radians(),
|
||||
"pattern_angle" => h.pattern_angle = v.to_radians(),
|
||||
"pattern_scale" if v > 0.0 => h.pattern_scale = v,
|
||||
// The stored pattern lines are the FINAL rendered geometry (offsets,
|
||||
// base points and dashes in world units — see the prebaked path in
|
||||
// hatch_model_from_dxf), so changing the scale must rescale them by
|
||||
// the ratio or the edit has no visible effect (#415).
|
||||
"pattern_scale" if v > 0.0 => {
|
||||
let old = h.pattern_scale;
|
||||
if old > 1e-12 {
|
||||
let k = v / old;
|
||||
for line in h.pattern.lines.iter_mut() {
|
||||
line.base_point.x *= k;
|
||||
line.base_point.y *= k;
|
||||
line.offset.x *= k;
|
||||
line.offset.y *= k;
|
||||
for d in line.dash_lengths.iter_mut() {
|
||||
*d *= k;
|
||||
}
|
||||
}
|
||||
}
|
||||
h.pattern_scale = v;
|
||||
}
|
||||
// Scale every pattern line's offset so the first line's spacing = v,
|
||||
// preserving the relative spacing between lines.
|
||||
"spacing" if v > 0.0 => {
|
||||
|
||||
+37
-1
@@ -708,7 +708,22 @@ impl Scene {
|
||||
if let Some(bg) = crate::entities::hatch::background_color(dxf) {
|
||||
let mut b = m.clone();
|
||||
b.pattern = model::hatch_model::HatchPattern::Solid;
|
||||
b.color = crate::scene::convert::tess_util::aci_to_rgba(&bg);
|
||||
// ByLayer / ByBlock backgrounds resolve through the
|
||||
// normal style chain instead of the raw ACI table
|
||||
// (#415).
|
||||
b.color = match bg {
|
||||
acadrust::types::Color::ByLayer => {
|
||||
crate::scene::view::render::layer_render_style(
|
||||
&self.document,
|
||||
&dxf.common.layer,
|
||||
)
|
||||
.color
|
||||
}
|
||||
acadrust::types::Color::ByBlock => self.render_style(e).0,
|
||||
other => {
|
||||
crate::scene::convert::tess_util::aci_to_rgba(&other)
|
||||
}
|
||||
};
|
||||
b.name = "SOLID".into();
|
||||
backdrop = Some(b);
|
||||
}
|
||||
@@ -1585,6 +1600,27 @@ impl Scene {
|
||||
self.bump_geometry();
|
||||
}
|
||||
|
||||
/// Rebuild the cached fill model (hatch / DXF SOLID) for `handle` after
|
||||
/// its document entity was edited in place. The fill models are prebuilt
|
||||
/// at load, so a pattern-scale / background / boundary edit stays
|
||||
/// invisible until the cached model is refreshed (#415).
|
||||
pub fn refresh_fill_model(&mut self, handle: Handle) {
|
||||
let new_model = match self.document.get_entity(handle) {
|
||||
Some(EntityType::Hatch(dxf)) => {
|
||||
let color = convert::tess_util::aci_to_rgba(&dxf.common.color);
|
||||
Self::hatch_model_from_dxf(dxf, color)
|
||||
}
|
||||
Some(EntityType::Solid(s)) => {
|
||||
let color = convert::tess_util::aci_to_rgba(&s.common.color);
|
||||
Some(Self::solid_hatch_model(s, color))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(model) = new_model {
|
||||
self.hatches.insert(handle, model);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_hatches_from_document(&mut self) {
|
||||
self.hatches.clear();
|
||||
|
||||
|
||||
@@ -430,7 +430,10 @@ impl PropertiesPanel {
|
||||
},
|
||||
Message::PropBgColorChanged,
|
||||
Message::PropBgColorPickerToggle,
|
||||
Message::PropBgColorPickerToggle,
|
||||
// "More Colors…" opens the full palette window targeting the
|
||||
// background colour — this used to just close the picker
|
||||
// (#415).
|
||||
Message::OpenColorWindow(crate::app::ColorPickTarget::PropertiesBg),
|
||||
);
|
||||
return prop_row_widget(label, selector);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user