forked from 1b-agent/opencadstudio-upstream
fix(layout): complete paperspace integration
Apply each layout's embedded page setup and plot style consistently across display, PDF, printing, and multi-layout output.\n\nRefs #844
This commit is contained in:
1 parent
9c8f841f71
commit
8c61d89b1e
18 files changed
+1393
-373
No files matched your search
+2
-19
@@ -72,8 +72,7 @@ impl Scene {
|
||||
// Remove the Layout object itself.
|
||||
self.document.objects.remove(&layout_handle);
|
||||
|
||||
// Drop the layout's entry from the ACAD_LAYOUT dictionary so it does not
|
||||
// dangle (and so AutoCAD doesn't try to recover a now-missing layout).
|
||||
// Drop the layout's dictionary entry so it does not dangle.
|
||||
let dict_handle = self.document.header.acad_layout_dict_handle;
|
||||
if let Some(ObjectType::Dictionary(d)) = self.document.objects.get_mut(&dict_handle) {
|
||||
d.entries.retain(|(k, _)| k != name);
|
||||
@@ -90,20 +89,6 @@ impl Scene {
|
||||
self.document.block_records.remove(&bn);
|
||||
}
|
||||
|
||||
// Drop any standalone PlotSettings page setup tied to this layout.
|
||||
let ps_handles: Vec<Handle> = self
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|(h, o)| match o {
|
||||
ObjectType::PlotSettings(ps) if ps.page_name == name => Some(*h),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
for h in ps_handles {
|
||||
self.document.objects.remove(&h);
|
||||
}
|
||||
|
||||
// If the deleted layout was active, fall back to Model space.
|
||||
if self.current_layout == name {
|
||||
self.current_layout = "Model".to_string();
|
||||
@@ -498,9 +483,7 @@ impl Scene {
|
||||
|
||||
/// Reconstruct a `pane_grid` configuration from a set of (tile-index, rect)
|
||||
/// items covering `region`, by recursively finding a full vertical or
|
||||
/// horizontal guillotine cut. AutoCAD tiled configs (and anything pane_grid
|
||||
/// produces) are guillotine layouts, so this round-trips them. A non-guillotine
|
||||
/// set falls back to chaining panes so nothing is lost.
|
||||
/// horizontal guillotine cut. Non-guillotine input falls back to chained panes.
|
||||
fn config_from_rects(
|
||||
items: &[(usize, iced::Rectangle)],
|
||||
region: iced::Rectangle,
|
||||
|
||||
@@ -91,6 +91,7 @@ impl Scene {
|
||||
self.document.header.limit_check = enabled;
|
||||
} else {
|
||||
self.document.header.paper_space_limit_check = enabled;
|
||||
self.persist_current_layout_state();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+181
-19
@@ -1614,6 +1614,15 @@ pub struct Scene {
|
||||
/// movement scanned the whole document for wipeouts and recreated the same
|
||||
/// Arcs, making Paper frame construction CPU-bound on large drawings.
|
||||
paper_sheet_render_cache: RefCell<HashMap<String, PaperSheetRenderCache>>,
|
||||
display_plot_style_cache:
|
||||
RefCell<HashMap<(String, String), Option<Arc<crate::io::plot_style::PlotStyleTable>>>>,
|
||||
styled_wire_cache: RefCell<
|
||||
HashMap<(u64, String), (u64, Arc<Vec<WireModel>>)>,
|
||||
>,
|
||||
styled_hatch_cache:
|
||||
RefCell<HashMap<(u64, usize, usize, String, u32), Arc<Vec<HatchModel>>>>,
|
||||
styled_wire_fill_cache:
|
||||
RefCell<HashMap<(u64, String, u32), Arc<Vec<HatchModel>>>>,
|
||||
/// Per-viewport projected wire cache for paper-space content viewports.
|
||||
/// Stores projected + clipped wires in paper-space coordinates.
|
||||
/// Maps vp_handle → (geometry_epoch, Vec<WireModel>).
|
||||
@@ -1880,6 +1889,10 @@ impl Scene {
|
||||
paper_sheet_cache: RefCell::new(HashMap::default()),
|
||||
paper_viewport_cache: RefCell::new(HashMap::default()),
|
||||
paper_sheet_render_cache: RefCell::new(HashMap::default()),
|
||||
display_plot_style_cache: RefCell::new(HashMap::default()),
|
||||
styled_wire_cache: RefCell::new(HashMap::default()),
|
||||
styled_hatch_cache: RefCell::new(HashMap::default()),
|
||||
styled_wire_fill_cache: RefCell::new(HashMap::default()),
|
||||
paper_projected_cache: RefCell::new(HashMap::default()),
|
||||
current_layout: "Model".to_string(),
|
||||
block_edit_block: None,
|
||||
@@ -2878,7 +2891,9 @@ impl Scene {
|
||||
/// active layout or its background are dropped.
|
||||
pub fn set_current_layout(&mut self, name: String) {
|
||||
if self.current_layout != name {
|
||||
self.persist_current_layout_state();
|
||||
self.current_layout = name;
|
||||
self.load_current_layout_state();
|
||||
self.sync_active_space_to_document();
|
||||
self.recolor_meshes();
|
||||
*self.wire_cache.borrow_mut() = None;
|
||||
@@ -2888,6 +2903,91 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_current_layout_state(&mut self) {
|
||||
self.display_plot_style_cache
|
||||
.borrow_mut()
|
||||
.retain(|(layout, _), _| layout != &self.current_layout);
|
||||
self.styled_wire_cache.borrow_mut().clear();
|
||||
self.styled_hatch_cache.borrow_mut().clear();
|
||||
self.styled_wire_fill_cache.borrow_mut().clear();
|
||||
if self.current_layout == "Model" {
|
||||
return;
|
||||
}
|
||||
if let Some((flags, insertion_base, min_extents, max_extents, min_limits, max_limits)) =
|
||||
self.document.objects.values().find_map(|object| {
|
||||
let ObjectType::Layout(layout) = object else {
|
||||
return None;
|
||||
};
|
||||
(layout.name == self.current_layout).then_some((
|
||||
layout.flags,
|
||||
layout.insertion_base,
|
||||
layout.min_extents,
|
||||
layout.max_extents,
|
||||
layout.min_limits,
|
||||
layout.max_limits,
|
||||
))
|
||||
}) {
|
||||
self.document.header.paper_space_linetype_scaling = flags & 1 != 0;
|
||||
self.document.header.paper_space_limit_check = flags & 2 != 0;
|
||||
self.document.header.paper_space_insertion_base = acadrust::types::Vector3::new(
|
||||
insertion_base.0,
|
||||
insertion_base.1,
|
||||
insertion_base.2,
|
||||
);
|
||||
self.document.header.paper_space_extents_min = acadrust::types::Vector3::new(
|
||||
min_extents.0,
|
||||
min_extents.1,
|
||||
min_extents.2,
|
||||
);
|
||||
self.document.header.paper_space_extents_max = acadrust::types::Vector3::new(
|
||||
max_extents.0,
|
||||
max_extents.1,
|
||||
max_extents.2,
|
||||
);
|
||||
self.document.header.paper_space_limits_min =
|
||||
acadrust::types::Vector2::new(min_limits.0, min_limits.1);
|
||||
self.document.header.paper_space_limits_max =
|
||||
acadrust::types::Vector2::new(max_limits.0, max_limits.1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persist_current_layout_state(&mut self) {
|
||||
if self.current_layout == "Model" {
|
||||
return;
|
||||
}
|
||||
let header = &self.document.header;
|
||||
let psltscale = header.paper_space_linetype_scaling;
|
||||
let plimcheck = header.paper_space_limit_check;
|
||||
let insertion_base = header.paper_space_insertion_base;
|
||||
let min_extents = header.paper_space_extents_min;
|
||||
let max_extents = header.paper_space_extents_max;
|
||||
let min_limits = header.paper_space_limits_min;
|
||||
let max_limits = header.paper_space_limits_max;
|
||||
for object in self.document.objects.values_mut() {
|
||||
let ObjectType::Layout(layout) = object else {
|
||||
continue;
|
||||
};
|
||||
if layout.name == self.current_layout {
|
||||
layout.flags = if psltscale {
|
||||
layout.flags | 1
|
||||
} else {
|
||||
layout.flags & !1
|
||||
};
|
||||
layout.flags = if plimcheck {
|
||||
layout.flags | 2
|
||||
} else {
|
||||
layout.flags & !2
|
||||
};
|
||||
layout.insertion_base = (insertion_base.x, insertion_base.y, insertion_base.z);
|
||||
layout.min_extents = (min_extents.x, min_extents.y, min_extents.z);
|
||||
layout.max_extents = (max_extents.x, max_extents.y, max_extents.z);
|
||||
layout.min_limits = (min_limits.x, min_limits.y);
|
||||
layout.max_limits = (max_limits.x, max_limits.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirror the active space (Model tile-mode vs a paper layout) into the
|
||||
/// document's persisted settings so it round-trips on save: the `$TILEMODE`
|
||||
/// header (`show_model_space`) and the `CTAB` current-tab variable. The
|
||||
@@ -3305,32 +3405,17 @@ impl Scene {
|
||||
Some(wire)
|
||||
}
|
||||
|
||||
/// The effective plot settings for the current layout: a standalone
|
||||
/// PlotSettings page setup if one exists, otherwise the settings embedded in
|
||||
/// the LAYOUT object (paper size, margins, origin, rotation, scale). Loaded
|
||||
/// The fallback preserves rotation, origin and scale from loaded files.
|
||||
/// The effective plot settings embedded in the current layout.
|
||||
pub fn effective_plot_settings(&self) -> Option<acadrust::objects::PlotSettings> {
|
||||
self.plot_settings_for(&self.current_layout)
|
||||
}
|
||||
|
||||
/// Plot settings for a specific layout by name: its standalone
|
||||
/// `PlotSettings` object if one exists, else synthesized from the `Layout`
|
||||
/// object's embedded fields.
|
||||
/// Plot settings embedded in a specific layout.
|
||||
pub fn plot_settings_for(&self, name: &str) -> Option<acadrust::objects::PlotSettings> {
|
||||
use acadrust::objects::{
|
||||
ObjectType, PaperMargin, PlotPaperUnits, PlotRotation, PlotSettings, PlotType,
|
||||
PlotWindow, ScaledType,
|
||||
PlotWindow, ScaledType, ShadePlotMode, ShadePlotResolutionLevel,
|
||||
};
|
||||
if let Some(ps) = self.document.objects.values().find_map(|o| {
|
||||
if let ObjectType::PlotSettings(ps) = o {
|
||||
if ps.page_name.as_str() == name {
|
||||
return Some(ps.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}) {
|
||||
return Some(ps);
|
||||
}
|
||||
self.document.objects.values().find_map(|o| {
|
||||
let ObjectType::Layout(l) = o else {
|
||||
return None;
|
||||
@@ -3338,10 +3423,13 @@ impl Scene {
|
||||
if l.name.as_str() != name {
|
||||
return None;
|
||||
}
|
||||
let mut ps = PlotSettings::new(l.name.clone());
|
||||
let mut ps = PlotSettings::new(l.plot_page_name.clone());
|
||||
ps.printer_name = l.plot_printer_name.clone();
|
||||
ps.paper_width = l.paper_width;
|
||||
ps.paper_height = l.paper_height;
|
||||
ps.paper_size = l.paper_size.clone();
|
||||
ps.plot_view_name = l.plot_view_name.clone();
|
||||
ps.current_style_sheet = l.plot_style_sheet.clone();
|
||||
ps.margins = PaperMargin::new(
|
||||
l.plot_margin_left,
|
||||
l.plot_margin_bottom,
|
||||
@@ -3362,10 +3450,84 @@ impl Scene {
|
||||
ps.scale_type = ScaledType::from_code(l.plot_scale_type);
|
||||
ps.scale_numerator = l.plot_scale_numerator;
|
||||
ps.scale_denominator = l.plot_scale_denominator;
|
||||
ps.flags = l.plot_flags;
|
||||
ps.standard_scale_factor = l.plot_scale_factor;
|
||||
ps.paper_image_origin_x = l.paper_image_origin_x;
|
||||
ps.paper_image_origin_y = l.paper_image_origin_y;
|
||||
ps.shade_plot_mode = ShadePlotMode::from_code(l.shade_plot_mode);
|
||||
ps.shade_plot_resolution =
|
||||
ShadePlotResolutionLevel::from_code(l.shade_plot_resolution);
|
||||
ps.shade_plot_dpi = l.shade_plot_dpi;
|
||||
ps.plot_view_handle = l.plot_view_handle;
|
||||
ps.visual_style_handle = l.visual_style_handle;
|
||||
Some(ps)
|
||||
})
|
||||
}
|
||||
|
||||
/// Replace the plot-settings portion embedded in one layout.
|
||||
pub fn set_layout_plot_settings(
|
||||
&mut self,
|
||||
name: &str,
|
||||
ps: &acadrust::objects::PlotSettings,
|
||||
) -> bool {
|
||||
let Some(layout) = self.document.objects.values_mut().find_map(|object| {
|
||||
let ObjectType::Layout(layout) = object else {
|
||||
return None;
|
||||
};
|
||||
(layout.name == name).then_some(layout)
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
layout.plot_page_name = ps.page_name.clone();
|
||||
layout.plot_printer_name = ps.printer_name.clone();
|
||||
layout.paper_size = ps.paper_size.clone();
|
||||
layout.plot_view_name = ps.plot_view_name.clone();
|
||||
layout.plot_style_sheet = ps.current_style_sheet.clone();
|
||||
layout.plot_margin_left = ps.margins.left;
|
||||
layout.plot_margin_bottom = ps.margins.bottom;
|
||||
layout.plot_margin_right = ps.margins.right;
|
||||
layout.plot_margin_top = ps.margins.top;
|
||||
layout.paper_width = ps.paper_width;
|
||||
layout.paper_height = ps.paper_height;
|
||||
layout.plot_origin_x = ps.origin_x;
|
||||
layout.plot_origin_y = ps.origin_y;
|
||||
layout.plot_window_min_x = ps.plot_window.lower_left_x;
|
||||
layout.plot_window_min_y = ps.plot_window.lower_left_y;
|
||||
layout.plot_window_max_x = ps.plot_window.upper_right_x;
|
||||
layout.plot_window_max_y = ps.plot_window.upper_right_y;
|
||||
layout.plot_scale_numerator = ps.scale_numerator;
|
||||
layout.plot_scale_denominator = ps.scale_denominator;
|
||||
layout.plot_paper_units = ps.paper_units.to_code();
|
||||
layout.plot_rotation = ps.rotation.to_code();
|
||||
layout.plot_type = ps.plot_type.to_code();
|
||||
layout.plot_scale_type = ps.scale_type.to_code();
|
||||
layout.shade_plot_mode = ps.shade_plot_mode.to_code();
|
||||
layout.shade_plot_resolution = ps.shade_plot_resolution.to_code();
|
||||
layout.shade_plot_dpi = ps.shade_plot_dpi;
|
||||
layout.plot_flags = ps.flags;
|
||||
layout.plot_scale_factor = ps.standard_scale_factor;
|
||||
layout.paper_image_origin_x = ps.paper_image_origin_x;
|
||||
layout.paper_image_origin_y = ps.paper_image_origin_y;
|
||||
layout.plot_view_handle = ps.plot_view_handle;
|
||||
layout.visual_style_handle = ps.visual_style_handle;
|
||||
layout.raw_plot_settings_codes = None;
|
||||
self.display_plot_style_cache
|
||||
.borrow_mut()
|
||||
.retain(|(layout_name, _), _| layout_name != name);
|
||||
self.styled_wire_cache.borrow_mut().clear();
|
||||
self.styled_hatch_cache.borrow_mut().clear();
|
||||
self.styled_wire_fill_cache.borrow_mut().clear();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn invalidate_display_plot_style(&self) {
|
||||
self.display_plot_style_cache.borrow_mut().clear();
|
||||
self.styled_wire_cache.borrow_mut().clear();
|
||||
self.styled_hatch_cache.borrow_mut().clear();
|
||||
self.styled_wire_fill_cache.borrow_mut().clear();
|
||||
}
|
||||
|
||||
/// PlotSettings store the paper size and plot margins in millimetres, but a
|
||||
/// layout's paper space is laid out in its own units — its viewports,
|
||||
/// drawing limits and the sheet the user sees can be in inches, millimetres,
|
||||
|
||||
@@ -139,6 +139,39 @@ impl GradientKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn plot_style_fill_pattern(style: u8) -> Option<HatchPattern> {
|
||||
let family = |angle_deg, dx, dy, dashes: &[f32]| PatFamily {
|
||||
angle_deg,
|
||||
x0: 0.0,
|
||||
y0: 0.0,
|
||||
dx,
|
||||
dy,
|
||||
dashes: dashes.to_vec(),
|
||||
};
|
||||
let pattern = match style {
|
||||
64 => HatchPattern::Solid,
|
||||
65 => HatchPattern::Pattern(vec![
|
||||
family(0.0, 0.0, 2.0, &[2.0, -2.0]),
|
||||
family(90.0, 2.0, 0.0, &[2.0, -2.0]),
|
||||
]),
|
||||
66 => HatchPattern::Pattern(vec![
|
||||
family(0.0, 0.0, 2.0, &[]),
|
||||
family(90.0, 2.0, 0.0, &[]),
|
||||
]),
|
||||
67 => HatchPattern::Pattern(vec![
|
||||
family(45.0, 0.0, 2.0, &[]),
|
||||
family(135.0, 0.0, 2.0, &[]),
|
||||
]),
|
||||
68 => HatchPattern::Pattern(vec![family(0.0, 0.0, 2.0, &[])]),
|
||||
69 => HatchPattern::Pattern(vec![family(135.0, 0.0, 2.0, &[])]),
|
||||
70 => HatchPattern::Pattern(vec![family(45.0, 0.0, 2.0, &[])]),
|
||||
71 => HatchPattern::Pattern(vec![family(0.0, 0.0, 2.0, &[0.0, -2.0])]),
|
||||
72 => HatchPattern::Pattern(vec![family(90.0, 2.0, 0.0, &[])]),
|
||||
_ => return None,
|
||||
};
|
||||
Some(pattern)
|
||||
}
|
||||
|
||||
/// Hatch fill pattern.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HatchPattern {
|
||||
|
||||
+271
-3
@@ -22,6 +22,8 @@ use crate::scene::{
|
||||
SceneLight, Uniforms, ViewportInstance, WireModel,
|
||||
};
|
||||
|
||||
const DISPLAY_STYLE_CACHE_LIMIT: usize = if cfg!(target_arch = "wasm32") { 4 } else { 24 };
|
||||
|
||||
// ── Camera hover state (shader::Program::State) ───────────────────────────
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -2496,6 +2498,238 @@ impl Scene {
|
||||
};
|
||||
(final_color, pl, pat, lw, aci)
|
||||
}
|
||||
|
||||
fn display_plot_style(&self) -> Option<Arc<crate::io::plot_style::PlotStyleTable>> {
|
||||
if self.current_layout == "Model" {
|
||||
return None;
|
||||
}
|
||||
let settings = self.effective_plot_settings()?;
|
||||
if !settings.flags.show_plot_styles || settings.current_style_sheet.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let key = (
|
||||
self.current_layout.clone(),
|
||||
settings.current_style_sheet.to_ascii_lowercase(),
|
||||
);
|
||||
if let Some(style) = self.display_plot_style_cache.borrow().get(&key) {
|
||||
return style.clone();
|
||||
}
|
||||
let style = crate::io::plot_style::PlotStyleTable::load_named(
|
||||
&settings.current_style_sheet,
|
||||
)
|
||||
.ok()
|
||||
.map(Arc::new);
|
||||
self.display_plot_style_cache
|
||||
.borrow_mut()
|
||||
.insert(key, style.clone());
|
||||
style
|
||||
}
|
||||
|
||||
fn apply_display_plot_style(
|
||||
&self,
|
||||
color: &mut [f32; 4],
|
||||
aci: u8,
|
||||
style: &crate::io::plot_style::PlotStyleTable,
|
||||
) {
|
||||
if aci == 0 {
|
||||
return;
|
||||
}
|
||||
if let Some(rgb) = style.resolve_color(aci) {
|
||||
color[..3].copy_from_slice(&rgb);
|
||||
}
|
||||
let screening = style.resolve_screening(aci);
|
||||
for (channel, paper) in color[..3]
|
||||
.iter_mut()
|
||||
.zip(self.paper_bg_color[..3].iter())
|
||||
{
|
||||
*channel = *channel * screening + *paper * (1.0 - screening);
|
||||
}
|
||||
}
|
||||
|
||||
fn display_styled_wires(
|
||||
&self,
|
||||
source: Arc<Vec<WireModel>>,
|
||||
source_gen: u64,
|
||||
) -> (Arc<Vec<WireModel>>, u64) {
|
||||
let Some(style) = self.display_plot_style() else {
|
||||
return (source, source_gen);
|
||||
};
|
||||
let key = (source_gen, style.name.to_ascii_lowercase());
|
||||
if let Some((gen, wires)) = self.styled_wire_cache.borrow().get(&key) {
|
||||
return (Arc::clone(wires), *gen);
|
||||
}
|
||||
let mut wires = source.as_ref().clone();
|
||||
for wire in &mut wires {
|
||||
self.apply_display_plot_style(&mut wire.color, wire.aci, &style);
|
||||
if wire.aci > 0 {
|
||||
if wire.fill_is_2d_solid
|
||||
&& style
|
||||
.aci_entries
|
||||
.get(wire.aci as usize)
|
||||
.is_some_and(|entry| (65..=72).contains(&entry.fill_style))
|
||||
{
|
||||
wire.fill_tris.clear();
|
||||
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);
|
||||
}
|
||||
for vertex in &mut wire.text_verts {
|
||||
self.apply_display_plot_style(&mut vertex.color, wire.aci, &style);
|
||||
}
|
||||
}
|
||||
}
|
||||
let wires = Arc::new(wires);
|
||||
let gen = crate::scene::WIRE_CONTENT_GEN
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let mut cache = self.styled_wire_cache.borrow_mut();
|
||||
if cache.len() >= DISPLAY_STYLE_CACHE_LIMIT {
|
||||
cache.clear();
|
||||
}
|
||||
cache.insert(key, (gen, Arc::clone(&wires)));
|
||||
(wires, gen)
|
||||
}
|
||||
|
||||
fn display_styled_hatches(
|
||||
&self,
|
||||
source: Arc<Vec<HatchModel>>,
|
||||
wire_fills: Option<&Arc<Vec<HatchModel>>>,
|
||||
pattern_scale: f32,
|
||||
) -> Arc<Vec<HatchModel>> {
|
||||
let Some(style) = self.display_plot_style() else {
|
||||
return source;
|
||||
};
|
||||
let key = (
|
||||
self.geometry_epoch,
|
||||
Arc::as_ptr(&source) as usize,
|
||||
wire_fills.map_or(0, |fills| Arc::as_ptr(fills) as usize),
|
||||
style.name.to_ascii_lowercase(),
|
||||
pattern_scale.to_bits(),
|
||||
);
|
||||
if let Some(hatches) = self.styled_hatch_cache.borrow().get(&key) {
|
||||
return Arc::clone(hatches);
|
||||
}
|
||||
let mut hatches = source.as_ref().clone();
|
||||
for hatch in &mut hatches {
|
||||
self.apply_display_plot_style(&mut hatch.color, hatch.aci, &style);
|
||||
if hatch.aci > 0 {
|
||||
if matches!(hatch.pattern, crate::scene::model::hatch_model::HatchPattern::Solid) {
|
||||
if let Some(fill_style) = style
|
||||
.aci_entries
|
||||
.get(hatch.aci as usize)
|
||||
.and_then(|entry| {
|
||||
crate::scene::model::hatch_model::plot_style_fill_pattern(
|
||||
entry.fill_style,
|
||||
)
|
||||
})
|
||||
{
|
||||
hatch.pattern = fill_style;
|
||||
hatch.scale = pattern_scale;
|
||||
}
|
||||
}
|
||||
if let Some(mm) = style.resolve_lineweight(hatch.aci) {
|
||||
hatch.line_weight_px = (mm * (96.0 / 25.4) * 2.0).max(1.0);
|
||||
}
|
||||
if let crate::scene::model::hatch_model::HatchPattern::Gradient {
|
||||
color2, ..
|
||||
} = &mut hatch.pattern
|
||||
{
|
||||
self.apply_display_plot_style(color2, hatch.aci, &style);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(wire_fills) = wire_fills {
|
||||
hatches.extend(wire_fills.iter().cloned());
|
||||
}
|
||||
let hatches = Arc::new(hatches);
|
||||
let mut cache = self.styled_hatch_cache.borrow_mut();
|
||||
cache.retain(|(epoch, _, _, _, _), _| *epoch == self.geometry_epoch);
|
||||
if cache.len() >= DISPLAY_STYLE_CACHE_LIMIT * 2 {
|
||||
cache.clear();
|
||||
}
|
||||
cache.insert(key, Arc::clone(&hatches));
|
||||
hatches
|
||||
}
|
||||
|
||||
fn display_styled_wire_fills(
|
||||
&self,
|
||||
wires: &Arc<Vec<WireModel>>,
|
||||
source_gen: u64,
|
||||
pattern_scale: f32,
|
||||
) -> Option<Arc<Vec<HatchModel>>> {
|
||||
let Some(style) = self.display_plot_style() else {
|
||||
return None;
|
||||
};
|
||||
let key = (
|
||||
source_gen,
|
||||
style.name.to_ascii_lowercase(),
|
||||
pattern_scale.to_bits(),
|
||||
);
|
||||
if let Some(hatches) = self.styled_wire_fill_cache.borrow().get(&key) {
|
||||
return Some(Arc::clone(hatches));
|
||||
}
|
||||
let mut hatches = Vec::new();
|
||||
for wire in wires.iter().filter(|wire| wire.fill_is_2d_solid && wire.aci > 0) {
|
||||
let Some(pattern) = style
|
||||
.aci_entries
|
||||
.get(wire.aci as usize)
|
||||
.and_then(|entry| {
|
||||
(65..=72)
|
||||
.contains(&entry.fill_style)
|
||||
.then(|| {
|
||||
crate::scene::model::hatch_model::plot_style_fill_pattern(
|
||||
entry.fill_style,
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let mut color = wire.color;
|
||||
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))
|
||||
.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);
|
||||
for (point_index, point) in triangle.iter().enumerate() {
|
||||
let index = triangle_index * 3 + point_index;
|
||||
let low = wire.fill_tris_low.get(index).copied().unwrap_or([0.0; 3]);
|
||||
boundary.push([point[0] + low[0], point[1] + low[1]]);
|
||||
}
|
||||
boundary.push(boundary[0]);
|
||||
hatches.push(HatchModel {
|
||||
render_instance: wire.render_instance.clone(),
|
||||
world_origin: [0.0, 0.0],
|
||||
boundary: Arc::new(boundary),
|
||||
boundary_wcs: None,
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
boundary_paths: None,
|
||||
style: acadrust::entities::HatchStyleType::Normal,
|
||||
pattern: pattern.clone(),
|
||||
name: "PLOTSTYLE".to_string(),
|
||||
color,
|
||||
aci: 0,
|
||||
line_weight_px,
|
||||
angle_offset: 0.0,
|
||||
scale: pattern_scale,
|
||||
draw_depth: wire.depth_override.unwrap_or(0.0),
|
||||
});
|
||||
}
|
||||
}
|
||||
let hatches = Arc::new(hatches);
|
||||
let mut cache = self.styled_wire_fill_cache.borrow_mut();
|
||||
if cache.len() >= DISPLAY_STYLE_CACHE_LIMIT {
|
||||
cache.clear();
|
||||
}
|
||||
cache.insert(key, Arc::clone(&hatches));
|
||||
Some(hatches)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an entity sits on a locked layer (via the document's layer table).
|
||||
@@ -3403,11 +3637,15 @@ impl Scene {
|
||||
} else {
|
||||
self.model_wires_for_viewport_arc(inst.handle, full.height)
|
||||
};
|
||||
let source_gen = self.last_model_wire_gen.get();
|
||||
let styled_fill_source = Arc::clone(&base_arc);
|
||||
let (base_arc, styled_gen) = self.display_styled_wires(base_arc, source_gen);
|
||||
self.last_model_wire_gen.set(styled_gen);
|
||||
// Wire-buffer content id for the upload gate. Preview / interim wires
|
||||
// are NOT part of this buffer anymore (they go in a separate per-frame
|
||||
// overlay buffer below), so the base id is the source's stable content
|
||||
// gen — a drag or camera move never re-uploads the base wire set.
|
||||
let base_wire_content_id = self.last_model_wire_gen.get();
|
||||
let base_wire_content_id = styled_gen;
|
||||
let base_wire_patch = self.model_wire_patch_for(base_wire_content_id);
|
||||
// Split Face3D wires from the rest. The split is content-only (keyed
|
||||
// by the wire-set content id), so while the geometry is unchanged it's
|
||||
@@ -3548,8 +3786,15 @@ impl Scene {
|
||||
width: full.width.max(1.0),
|
||||
height: full.height.max(1.0),
|
||||
};
|
||||
let mut uniforms =
|
||||
Uniforms::new(&inst.camera, full_bounds, self.document.header.lineweight_display);
|
||||
let display_plot_lineweights = self.current_layout != "Model"
|
||||
&& self.effective_plot_settings().is_some_and(|settings| {
|
||||
settings.flags.show_plot_styles && settings.flags.print_lineweights
|
||||
});
|
||||
let mut uniforms = Uniforms::new(
|
||||
&inst.camera,
|
||||
full_bounds,
|
||||
self.document.header.lineweight_display || display_plot_lineweights,
|
||||
);
|
||||
if self.document.header.paper_space_linetype_scaling
|
||||
&& !inst.paper_sheet
|
||||
&& inst.tile_idx.is_none()
|
||||
@@ -3642,6 +3887,29 @@ impl Scene {
|
||||
None,
|
||||
)
|
||||
};
|
||||
let viewport_scale = if inst.paper_sheet {
|
||||
1.0
|
||||
} else {
|
||||
self.document
|
||||
.get_entity(inst.handle)
|
||||
.and_then(|entity| match entity {
|
||||
EntityType::Viewport(viewport) => Some(vp_effective_scale(
|
||||
viewport.custom_scale,
|
||||
viewport.view_height,
|
||||
viewport.height,
|
||||
)),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(1.0)
|
||||
};
|
||||
let pattern_scale = (self.paper_space_unit_factor() / viewport_scale.max(1.0e-9)) as f32;
|
||||
let styled_wire_fills =
|
||||
self.display_styled_wire_fills(&styled_fill_source, source_gen, pattern_scale);
|
||||
let hatches = self.display_styled_hatches(
|
||||
hatches,
|
||||
styled_wire_fills.as_ref().filter(|fills| !fills.is_empty()),
|
||||
pattern_scale,
|
||||
);
|
||||
let images = if let Some(images) = paper_images {
|
||||
images
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user