refactor(scene/mod.rs): pull entity-specific helpers into entities/
Five chunks moved out of the 5965-line scene/mod.rs to live with the entities they belong to: - wide_lwpolyline_fills → entities/lwpolyline.rs::wide_fills - wide_polyline2d_fills → entities/polyline.rs::wide_fills - polyline_segment_fill → entities/common.rs (shared by both above) - text_obb_corners_native + text_baseline_points + text_greek_obb_tris → entities/text_support.rs (handles Text/MText/Attribute alike) - append_insert_attribute_wires → entities/insert.rs Net: scene/mod.rs 5965 → 5404 lines (~560 lines extracted). Three helpers in scene/render.rs / scene/block_cache.rs widened from pub(super) to pub(crate) so the relocated functions can still reach them. scene::entity_aabb also widened for the same reason. scene::tessellate_entity_dim_text stays put — it's a thin wrapper around the private tessellate_entity dispatcher and moving it would require exposing tessellate_entity's full signature across the crate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
64d8b2117d
commit
40180e9089
8 changed files with 566 additions and 573 deletions
|
|
@ -167,3 +167,84 @@ pub fn ro_prop(label: &'static str, field: &'static str, value: impl Into<String
|
||||||
pub fn parse_f64(value: &str) -> Option<f64> {
|
pub fn parse_f64(value: &str) -> Option<f64> {
|
||||||
value.trim().parse::<f64>().ok()
|
value.trim().parse::<f64>().ok()
|
||||||
}
|
}
|
||||||
|
/// Compute the filled boundary polygon for one polyline segment.
|
||||||
|
/// For straight segments: a rectangle/trapezoid.
|
||||||
|
/// For arc segments: an arc band (outer arc + reversed inner arc).
|
||||||
|
/// Returns `None` if the segment is degenerate.
|
||||||
|
pub(crate) fn polyline_segment_fill(
|
||||||
|
p0: [f32; 2],
|
||||||
|
p1: [f32; 2],
|
||||||
|
hw0: f32,
|
||||||
|
hw1: f32,
|
||||||
|
bulge: f32,
|
||||||
|
) -> Option<Vec<[f32; 2]>> {
|
||||||
|
if bulge.abs() < 1e-9 {
|
||||||
|
// Straight segment — rectangle or trapezoid
|
||||||
|
let dx = p1[0] - p0[0];
|
||||||
|
let dy = p1[1] - p0[1];
|
||||||
|
let len = (dx * dx + dy * dy).sqrt();
|
||||||
|
if len < 1e-9 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let nx = -dy / len;
|
||||||
|
let ny = dx / len;
|
||||||
|
Some(vec![
|
||||||
|
[p0[0] + hw0 * nx, p0[1] + hw0 * ny],
|
||||||
|
[p1[0] + hw1 * nx, p1[1] + hw1 * ny],
|
||||||
|
[p1[0] - hw1 * nx, p1[1] - hw1 * ny],
|
||||||
|
[p0[0] - hw0 * nx, p0[1] - hw0 * ny],
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
// Arc segment — arc band polygon.
|
||||||
|
// Center math matches `bulge_to_arc` in modules/home/modify/explode.rs:
|
||||||
|
// r = chord * (1 + b²) / (4·|b|)
|
||||||
|
// d = r * (1 - b²) / (1 + b²) (signed: negative ⇒ major arc, center
|
||||||
|
// flips to the opposite side of chord)
|
||||||
|
// center = midpoint + sign(b) · d · left_perp(chord)
|
||||||
|
let b = bulge as f64;
|
||||||
|
let b2 = b * b;
|
||||||
|
let dx = (p1[0] - p0[0]) as f64;
|
||||||
|
let dy = (p1[1] - p0[1]) as f64;
|
||||||
|
let chord_len = (dx * dx + dy * dy).sqrt();
|
||||||
|
if chord_len < 1e-9 || b.abs() < 1e-12 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let r = chord_len * (1.0 + b2) / (4.0 * b.abs());
|
||||||
|
let d_perp = r * (1.0 - b2) / (1.0 + b2);
|
||||||
|
let mx = ((p0[0] + p1[0]) * 0.5) as f64;
|
||||||
|
let my = ((p0[1] + p1[1]) * 0.5) as f64;
|
||||||
|
let perp_x = -dy / chord_len;
|
||||||
|
let perp_y = dx / chord_len;
|
||||||
|
let sign = b.signum();
|
||||||
|
let cx = (mx + sign * d_perp * perp_x) as f32;
|
||||||
|
let cy = (my + sign * d_perp * perp_y) as f32;
|
||||||
|
let a0 = ((p0[1] - cy) as f32).atan2((p0[0] - cx) as f32);
|
||||||
|
let a1 = ((p1[1] - cy) as f32).atan2((p1[0] - cx) as f32);
|
||||||
|
let (sa, mut ea) = if bulge > 0.0 { (a0, a1) } else { (a1, a0) };
|
||||||
|
if ea < sa {
|
||||||
|
ea += std::f32::consts::TAU;
|
||||||
|
}
|
||||||
|
let span = ea - sa;
|
||||||
|
let segs = ((span.abs() / std::f32::consts::TAU) * 24.0)
|
||||||
|
.ceil()
|
||||||
|
.max(4.0) as u32;
|
||||||
|
let r = r as f32;
|
||||||
|
let r_outer = |t: f32| r + (hw0 + (hw1 - hw0) * t);
|
||||||
|
let r_inner = |t: f32| (r - (hw0 + (hw1 - hw0) * t)).max(0.0);
|
||||||
|
let mut boundary = Vec::with_capacity((segs as usize + 1) * 2);
|
||||||
|
let inv = 1.0 / segs as f32;
|
||||||
|
for j in 0..=segs {
|
||||||
|
let t = j as f32 * inv;
|
||||||
|
let ang = sa + span * t;
|
||||||
|
let ro = r_outer(t);
|
||||||
|
boundary.push([cx + ro * ang.cos(), cy + ro * ang.sin()]);
|
||||||
|
}
|
||||||
|
for j in (0..=segs).rev() {
|
||||||
|
let t = j as f32 * inv;
|
||||||
|
let ang = sa + span * t;
|
||||||
|
let ri = r_inner(t);
|
||||||
|
boundary.push([cx + ri * ang.cos(), cy + ri * ang.sin()]);
|
||||||
|
}
|
||||||
|
Some(boundary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
use acadrust::entities::Insert;
|
use acadrust::entities::Insert;
|
||||||
use acadrust::types::{Transform, Vector3};
|
use acadrust::types::{Transform, Vector3};
|
||||||
|
use acadrust::{EntityType, Handle};
|
||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
|
|
||||||
use crate::command::EntityTransform;
|
use crate::command::EntityTransform;
|
||||||
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
|
use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip};
|
||||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
|
||||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||||
|
use crate::scene::{block_cache, render, tessellate};
|
||||||
|
use crate::scene::wire_model::WireModel;
|
||||||
|
|
||||||
fn grips(ins: &Insert) -> Vec<GripDef> {
|
fn grips(ins: &Insert) -> Vec<GripDef> {
|
||||||
let p = Vec3::new(
|
let p = Vec3::new(
|
||||||
|
|
@ -142,3 +145,75 @@ impl crate::entities::traits::FallbackTess for Insert {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) fn append_insert_attribute_wires(
|
||||||
|
wires: &mut Vec<WireModel>,
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
ins: &acadrust::entities::Insert,
|
||||||
|
insert_handle: Handle,
|
||||||
|
sel: bool,
|
||||||
|
ins_color: [f32; 4],
|
||||||
|
ins_pat_len: f32,
|
||||||
|
ins_pat: [f32; 8],
|
||||||
|
ins_lw_px: f32,
|
||||||
|
bg_color: [f32; 4],
|
||||||
|
is_xref: bool,
|
||||||
|
pslt_factor: f32,
|
||||||
|
anno_scale: f32,
|
||||||
|
world_offset: [f64; 3],
|
||||||
|
) {
|
||||||
|
if ins.attributes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ATTMODE (header.attribute_visibility):
|
||||||
|
// 0 = Off — every attribute hidden
|
||||||
|
// 1 = Normal — honour per-attribute `invisible` flag (default)
|
||||||
|
// 2 = On — every attribute forced visible, ignoring its flag
|
||||||
|
let attmode = document.header.attribute_visibility;
|
||||||
|
if attmode == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for attr in &ins.attributes {
|
||||||
|
let per_attr_hidden = attr.common.invisible || attr.flags.invisible;
|
||||||
|
if attmode == 1 && per_attr_hidden {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let attr_entity = EntityType::AttributeEntity(attr.clone());
|
||||||
|
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) =
|
||||||
|
render::render_style_for_block_sub(
|
||||||
|
document,
|
||||||
|
&attr_entity,
|
||||||
|
ins_color,
|
||||||
|
ins_pat_len,
|
||||||
|
ins_pat,
|
||||||
|
ins_lw_px,
|
||||||
|
);
|
||||||
|
let sub_color = render::adapt_to_bg(sub_color, bg_color);
|
||||||
|
let sub_color = if is_xref && !sel {
|
||||||
|
block_cache::fade_toward_bg(sub_color, bg_color)
|
||||||
|
} else {
|
||||||
|
sub_color
|
||||||
|
};
|
||||||
|
let sub_aabb = crate::scene::entity_aabb(&attr_entity, world_offset);
|
||||||
|
let mut attr_wires = tessellate::tessellate(
|
||||||
|
document,
|
||||||
|
insert_handle,
|
||||||
|
&attr_entity,
|
||||||
|
sel,
|
||||||
|
sub_color,
|
||||||
|
sub_plen * pslt_factor,
|
||||||
|
sub_pat.map(|v| v * pslt_factor),
|
||||||
|
sub_lw_px,
|
||||||
|
world_offset,
|
||||||
|
anno_scale,
|
||||||
|
);
|
||||||
|
// Use the INSERT's handle so selection / picking groups attribute
|
||||||
|
// text with the parent insert instead of treating it as a stray text.
|
||||||
|
for w in &mut attr_wires {
|
||||||
|
w.name = insert_handle.value().to_string();
|
||||||
|
w.aci = sub_aci;
|
||||||
|
w.aabb = sub_aabb;
|
||||||
|
}
|
||||||
|
wires.extend(attr_wires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -516,3 +516,36 @@ impl Transformable for LwPolyline {
|
||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> Vec<Vec<[f32; 2]>> {
|
||||||
|
let hw_const = (pl.constant_width / 2.0) as f32;
|
||||||
|
let verts = &pl.vertices;
|
||||||
|
let n = verts.len();
|
||||||
|
if n < 2 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let seg_count = if pl.is_closed { n } else { n - 1 };
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for i in 0..seg_count {
|
||||||
|
let v0 = &verts[i];
|
||||||
|
let v1 = &verts[(i + 1) % n];
|
||||||
|
let hw0 = if v0.start_width > 1e-9 {
|
||||||
|
v0.start_width as f32 / 2.0
|
||||||
|
} else {
|
||||||
|
hw_const
|
||||||
|
};
|
||||||
|
let hw1 = if v0.end_width > 1e-9 {
|
||||||
|
v0.end_width as f32 / 2.0
|
||||||
|
} else {
|
||||||
|
hw_const
|
||||||
|
};
|
||||||
|
if hw0 < 1e-6 && hw1 < 1e-6 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let p0 = [v0.location.x as f32, v0.location.y as f32];
|
||||||
|
let p1 = [v1.location.x as f32, v1.location.y as f32];
|
||||||
|
if let Some(poly) = crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) {
|
||||||
|
out.push(poly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -596,3 +596,37 @@ impl Transformable for Polyline3D {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Generate solid-fill boundary polygons for each wide segment of a Polyline2D.
|
||||||
|
pub(crate) fn wide_fills(pl: &acadrust::entities::Polyline2D) -> Vec<Vec<[f32; 2]>> {
|
||||||
|
let hw_default = (pl.start_width.max(pl.end_width) / 2.0) as f32;
|
||||||
|
let verts = &pl.vertices;
|
||||||
|
let n = verts.len();
|
||||||
|
if n < 2 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let seg_count = if pl.is_closed() { n } else { n - 1 };
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for i in 0..seg_count {
|
||||||
|
let v0 = &verts[i];
|
||||||
|
let v1 = &verts[(i + 1) % n];
|
||||||
|
let hw0 = if v0.start_width > 1e-9 {
|
||||||
|
v0.start_width as f32 / 2.0
|
||||||
|
} else {
|
||||||
|
hw_default
|
||||||
|
};
|
||||||
|
let hw1 = if v0.end_width > 1e-9 {
|
||||||
|
v0.end_width as f32 / 2.0
|
||||||
|
} else {
|
||||||
|
hw_default
|
||||||
|
};
|
||||||
|
if hw0 < 1e-6 && hw1 < 1e-6 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let p0 = [v0.location.x as f32, v0.location.y as f32];
|
||||||
|
let p1 = [v1.location.x as f32, v1.location.y as f32];
|
||||||
|
if let Some(poly) = crate::entities::common::polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) {
|
||||||
|
out.push(poly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use acadrust::types::aci_table::aci_to_rgb;
|
use acadrust::types::aci_table::aci_to_rgb;
|
||||||
use acadrust::CadDocument;
|
use acadrust::{CadDocument, EntityType};
|
||||||
|
|
||||||
use crate::scene::acad_to_truck::TextStroke;
|
use crate::scene::acad_to_truck::TextStroke;
|
||||||
use crate::scene::cxf;
|
use crate::scene::cxf;
|
||||||
|
|
@ -1364,3 +1364,334 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||||
v_offset,
|
v_offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) fn text_obb_corners_native(
|
||||||
|
e: &EntityType,
|
||||||
|
anno_scale: f32,
|
||||||
|
mtext_lines_override: Option<usize>,
|
||||||
|
) -> Option<[[f64; 3]; 4]> {
|
||||||
|
use acadrust::entities::attribute_definition::{
|
||||||
|
HorizontalAlignment as AttrHA, VerticalAlignment as AttrVA,
|
||||||
|
};
|
||||||
|
use acadrust::entities::{AttachmentPoint, TextHorizontalAlignment, TextVerticalAlignment};
|
||||||
|
|
||||||
|
let anno = anno_scale as f64;
|
||||||
|
|
||||||
|
// Map Attribute alignment enums to the same h/v anchor fractions used by
|
||||||
|
// Text. Kept local because the Attribute enum is distinct from Text's.
|
||||||
|
let attr_h_anchor = |ha: AttrHA| -> f64 {
|
||||||
|
match ha {
|
||||||
|
AttrHA::Left => 0.0,
|
||||||
|
AttrHA::Center | AttrHA::Middle | AttrHA::Aligned | AttrHA::Fit => 0.5,
|
||||||
|
AttrHA::Right => 1.0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let attr_v_anchor = |va: AttrVA| -> f64 {
|
||||||
|
match va {
|
||||||
|
AttrVA::Baseline | AttrVA::Bottom => 0.0,
|
||||||
|
AttrVA::Middle => 0.5,
|
||||||
|
AttrVA::Top => 1.0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let attr_use_align_pt = |ha: AttrHA, va: AttrVA| -> bool {
|
||||||
|
!matches!((ha, va), (AttrHA::Left, AttrVA::Baseline))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (ix, iy, iz, w, h, rot, h_anchor, v_anchor) = match e {
|
||||||
|
EntityType::Text(t) => {
|
||||||
|
let h_world = t.height * anno;
|
||||||
|
let w_factor = if t.width_factor > 0.0 { t.width_factor } else { 1.0 };
|
||||||
|
let n = t.value.chars().count().max(1) as f64;
|
||||||
|
// Approximate glyph width: AutoCAD's stroke fonts average ~0.6 em.
|
||||||
|
let w_world = n * h_world * w_factor * 0.6;
|
||||||
|
let h_anchor = match t.horizontal_alignment {
|
||||||
|
TextHorizontalAlignment::Left => 0.0,
|
||||||
|
TextHorizontalAlignment::Center
|
||||||
|
| TextHorizontalAlignment::Middle
|
||||||
|
| TextHorizontalAlignment::Aligned
|
||||||
|
| TextHorizontalAlignment::Fit => 0.5,
|
||||||
|
TextHorizontalAlignment::Right => 1.0,
|
||||||
|
};
|
||||||
|
let v_anchor = match t.vertical_alignment {
|
||||||
|
TextVerticalAlignment::Baseline | TextVerticalAlignment::Bottom => 0.0,
|
||||||
|
TextVerticalAlignment::Middle => 0.5,
|
||||||
|
TextVerticalAlignment::Top => 1.0,
|
||||||
|
};
|
||||||
|
// AutoCAD writes `alignment_point` (DXF 11) whenever the text
|
||||||
|
// isn't simple Left+Baseline; in that case it — not the
|
||||||
|
// insertion_point — is the anchor the alignment fractions map to.
|
||||||
|
let use_alignment_pt = !matches!(
|
||||||
|
(t.horizontal_alignment, t.vertical_alignment),
|
||||||
|
(
|
||||||
|
TextHorizontalAlignment::Left,
|
||||||
|
TextVerticalAlignment::Baseline,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let anchor = match (use_alignment_pt, t.alignment_point) {
|
||||||
|
(true, Some(p)) => p,
|
||||||
|
_ => t.insertion_point,
|
||||||
|
};
|
||||||
|
(
|
||||||
|
anchor.x,
|
||||||
|
anchor.y,
|
||||||
|
anchor.z,
|
||||||
|
w_world,
|
||||||
|
h_world,
|
||||||
|
t.rotation,
|
||||||
|
h_anchor,
|
||||||
|
v_anchor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EntityType::AttributeDefinition(a) => {
|
||||||
|
let h_world = a.height * anno;
|
||||||
|
let w_factor = if a.width_factor > 0.0 { a.width_factor } else { 1.0 };
|
||||||
|
// Render the tag in preview when no default; matches `attribute.rs`.
|
||||||
|
let display = if a.default_value.is_empty() { &a.tag } else { &a.default_value };
|
||||||
|
let n = display.chars().count().max(1) as f64;
|
||||||
|
let w_world = n * h_world * w_factor * 0.6;
|
||||||
|
let h_anchor = attr_h_anchor(a.horizontal_alignment);
|
||||||
|
let v_anchor = attr_v_anchor(a.vertical_alignment);
|
||||||
|
let anchor = if attr_use_align_pt(a.horizontal_alignment, a.vertical_alignment) {
|
||||||
|
a.alignment_point
|
||||||
|
} else {
|
||||||
|
a.insertion_point
|
||||||
|
};
|
||||||
|
(
|
||||||
|
anchor.x, anchor.y, anchor.z, w_world, h_world, a.rotation, h_anchor, v_anchor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EntityType::AttributeEntity(a) => {
|
||||||
|
let h_world = a.height * anno;
|
||||||
|
let w_factor = if a.width_factor > 0.0 { a.width_factor } else { 1.0 };
|
||||||
|
let n = a.value.chars().count().max(1) as f64;
|
||||||
|
let w_world = n * h_world * w_factor * 0.6;
|
||||||
|
let h_anchor = attr_h_anchor(a.horizontal_alignment);
|
||||||
|
let v_anchor = attr_v_anchor(a.vertical_alignment);
|
||||||
|
let anchor = if attr_use_align_pt(a.horizontal_alignment, a.vertical_alignment) {
|
||||||
|
a.alignment_point
|
||||||
|
} else {
|
||||||
|
a.insertion_point
|
||||||
|
};
|
||||||
|
(
|
||||||
|
anchor.x, anchor.y, anchor.z, w_world, h_world, a.rotation, h_anchor, v_anchor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EntityType::Tolerance(t) => {
|
||||||
|
// Approximate: a feature control frame is roughly 1 line tall;
|
||||||
|
// width comes from char-count of the (already-stripped) text.
|
||||||
|
// GD&T symbols all advance one cell, so plain char count is close.
|
||||||
|
let raw_h = if t.text_height > 0.0 { t.text_height } else { 2.5 };
|
||||||
|
let h_world = raw_h * anno;
|
||||||
|
// Each cell ≈ 1.4 × height (matches `tolerance.rs` min_cell_w).
|
||||||
|
let n = t.text.chars().filter(|c| *c != '\n').count().max(1) as f64;
|
||||||
|
let w_world = h_world * 1.4 * n * 0.5; // rough — fine for LOD greek
|
||||||
|
// direction encodes rotation as a vector.
|
||||||
|
let rot = (t.direction.y).atan2(t.direction.x);
|
||||||
|
(
|
||||||
|
t.insertion_point.x,
|
||||||
|
t.insertion_point.y,
|
||||||
|
t.insertion_point.z,
|
||||||
|
w_world,
|
||||||
|
h_world * 1.5, // frame is taller than glyph cap by ~0.5 h
|
||||||
|
rot,
|
||||||
|
0.0, // anchored at insertion point (bottom-left)
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EntityType::MText(m) => {
|
||||||
|
let h_world = m.height * anno;
|
||||||
|
let raw_lines = (m.value.matches('\n').count() + 1) as f64;
|
||||||
|
let effective_lines = match mtext_lines_override {
|
||||||
|
Some(n) => n.max(1) as f64,
|
||||||
|
None => raw_lines,
|
||||||
|
};
|
||||||
|
let w_world = if m.rectangle_width > 0.0 {
|
||||||
|
m.rectangle_width
|
||||||
|
} else {
|
||||||
|
h_world * 8.0 * effective_lines.max(1.0)
|
||||||
|
};
|
||||||
|
// Wrap-aware override beats `rectangle_height` — the stored
|
||||||
|
// height can be stale on DWGs that were re-saved without
|
||||||
|
// updating the bounds.
|
||||||
|
let total_h = if mtext_lines_override.is_some() {
|
||||||
|
h_world * effective_lines.max(1.0) * m.line_spacing_factor.max(0.5)
|
||||||
|
} else {
|
||||||
|
m.rectangle_height.unwrap_or(
|
||||||
|
h_world * raw_lines.max(1.0) * m.line_spacing_factor.max(0.5),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
// MText `attachment_point` puts `insertion_point` at one of the
|
||||||
|
// 9 corners/midpoints of the text bbox. h_anchor / v_anchor are
|
||||||
|
// fractions from (left, bottom) of the bbox.
|
||||||
|
let (h_anchor, v_anchor) = match m.attachment_point {
|
||||||
|
AttachmentPoint::TopLeft => (0.0, 1.0),
|
||||||
|
AttachmentPoint::TopCenter => (0.5, 1.0),
|
||||||
|
AttachmentPoint::TopRight => (1.0, 1.0),
|
||||||
|
AttachmentPoint::MiddleLeft => (0.0, 0.5),
|
||||||
|
AttachmentPoint::MiddleCenter => (0.5, 0.5),
|
||||||
|
AttachmentPoint::MiddleRight => (1.0, 0.5),
|
||||||
|
AttachmentPoint::BottomLeft => (0.0, 0.0),
|
||||||
|
AttachmentPoint::BottomCenter => (0.5, 0.0),
|
||||||
|
AttachmentPoint::BottomRight => (1.0, 0.0),
|
||||||
|
};
|
||||||
|
(
|
||||||
|
m.insertion_point.x,
|
||||||
|
m.insertion_point.y,
|
||||||
|
m.insertion_point.z,
|
||||||
|
w_world,
|
||||||
|
total_h,
|
||||||
|
m.rotation,
|
||||||
|
h_anchor,
|
||||||
|
v_anchor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let x0 = -h_anchor * w;
|
||||||
|
let x1 = (1.0 - h_anchor) * w;
|
||||||
|
let y0 = -v_anchor * h;
|
||||||
|
let y1 = (1.0 - v_anchor) * h;
|
||||||
|
|
||||||
|
let (s, c) = (rot.sin(), rot.cos());
|
||||||
|
let rot_pt = |lx: f64, ly: f64| -> [f64; 3] {
|
||||||
|
let rx = lx * c - ly * s;
|
||||||
|
let ry = lx * s + ly * c;
|
||||||
|
[ix + rx, iy + ry, iz]
|
||||||
|
};
|
||||||
|
|
||||||
|
Some([
|
||||||
|
rot_pt(x0, y0),
|
||||||
|
rot_pt(x1, y0),
|
||||||
|
rot_pt(x1, y1),
|
||||||
|
rot_pt(x0, y1),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
pub(crate) fn text_baseline_points(
|
||||||
|
e: &EntityType,
|
||||||
|
anno_scale: f32,
|
||||||
|
world_offset: [f64; 3],
|
||||||
|
n_lines: usize,
|
||||||
|
) -> Vec<[f32; 3]> {
|
||||||
|
let Some(corners) = text_obb_corners_native(e, anno_scale, Some(n_lines)) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let line_h = match e {
|
||||||
|
EntityType::Text(t) => (t.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::MText(m) => (m.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::AttributeDefinition(a) => (a.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::AttributeEntity(a) => (a.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::Tolerance(t) => {
|
||||||
|
let raw = if t.text_height > 0.0 { t.text_height } else { 2.5 };
|
||||||
|
(raw * anno_scale as f64) as f32
|
||||||
|
}
|
||||||
|
_ => return vec![],
|
||||||
|
};
|
||||||
|
if line_h <= 0.0 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let n_lines = n_lines.max(1);
|
||||||
|
let [ox, oy, oz] = world_offset;
|
||||||
|
let cast = |p: [f64; 3]| -> [f32; 3] {
|
||||||
|
[(p[0] - ox) as f32, (p[1] - oy) as f32, (p[2] - oz) as f32]
|
||||||
|
};
|
||||||
|
let bl = cast(corners[0]);
|
||||||
|
let br = cast(corners[1]);
|
||||||
|
let full_tl = cast(corners[3]);
|
||||||
|
|
||||||
|
let (ux, uy, uz) = (full_tl[0] - bl[0], full_tl[1] - bl[1], full_tl[2] - bl[2]);
|
||||||
|
let ulen = (ux * ux + uy * uy + uz * uz).sqrt();
|
||||||
|
if ulen < 1e-9 {
|
||||||
|
return vec![bl, br];
|
||||||
|
}
|
||||||
|
let (nx, ny, nz) = (ux / ulen, uy / ulen, uz / ulen);
|
||||||
|
|
||||||
|
let mut pts = Vec::with_capacity(n_lines * 3);
|
||||||
|
for i in 0..n_lines {
|
||||||
|
// i = 0 is the topmost line — its bottom sits one line_h below
|
||||||
|
// the OBB top (≈ `ulen`). For n_lines > ulen/line_h the deepest
|
||||||
|
// baselines clamp to the OBB bottom.
|
||||||
|
let bot_off = ((i + 1) as f32) * line_h;
|
||||||
|
let along = (ulen - bot_off).max(0.0);
|
||||||
|
let p0 = [bl[0] + nx * along, bl[1] + ny * along, bl[2] + nz * along];
|
||||||
|
let p1 = [br[0] + nx * along, br[1] + ny * along, br[2] + nz * along];
|
||||||
|
if !pts.is_empty() {
|
||||||
|
pts.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
pts.extend_from_slice(&[p0, p1]);
|
||||||
|
}
|
||||||
|
pts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filled tris for a greeked top-level Text / MText. One 2-triangle rect
|
||||||
|
/// per visible line — `n_lines` is the actual rendered line count from
|
||||||
|
/// `mtext_line_count` (1 for Text). Stacked top → bottom along the OBB's
|
||||||
|
/// up direction. The face3d pipeline skips its 0.45 dim for wires with
|
||||||
|
/// empty `points`, so these tris render at the literal text color.
|
||||||
|
pub(crate) fn text_greek_obb_tris(
|
||||||
|
e: &EntityType,
|
||||||
|
anno_scale: f32,
|
||||||
|
world_offset: [f64; 3],
|
||||||
|
n_lines: usize,
|
||||||
|
) -> Vec<[f32; 3]> {
|
||||||
|
let Some(corners) = text_obb_corners_native(e, anno_scale, Some(n_lines)) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let line_h = match e {
|
||||||
|
EntityType::Text(t) => (t.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::MText(m) => (m.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::AttributeDefinition(a) => (a.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::AttributeEntity(a) => (a.height * anno_scale as f64) as f32,
|
||||||
|
EntityType::Tolerance(t) => {
|
||||||
|
let raw = if t.text_height > 0.0 { t.text_height } else { 2.5 };
|
||||||
|
(raw * anno_scale as f64) as f32
|
||||||
|
}
|
||||||
|
_ => return vec![],
|
||||||
|
};
|
||||||
|
if line_h <= 0.0 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let n_lines = n_lines.max(1);
|
||||||
|
let [ox, oy, oz] = world_offset;
|
||||||
|
let cast = |p: [f64; 3]| -> [f32; 3] {
|
||||||
|
[(p[0] - ox) as f32, (p[1] - oy) as f32, (p[2] - oz) as f32]
|
||||||
|
};
|
||||||
|
let bl = cast(corners[0]);
|
||||||
|
let br = cast(corners[1]);
|
||||||
|
let full_tl = cast(corners[3]);
|
||||||
|
|
||||||
|
let (ux, uy, uz) = (full_tl[0] - bl[0], full_tl[1] - bl[1], full_tl[2] - bl[2]);
|
||||||
|
let ulen = (ux * ux + uy * uy + uz * uz).sqrt();
|
||||||
|
if ulen < 1e-9 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let (nx, ny, nz) = (ux / ulen, uy / ulen, uz / ulen);
|
||||||
|
|
||||||
|
let mut tris = Vec::with_capacity(n_lines * 6);
|
||||||
|
for i in 0..n_lines {
|
||||||
|
let top_along = (ulen - (i as f32) * line_h).max(0.0);
|
||||||
|
let bot_along = (ulen - ((i + 1) as f32) * line_h).max(0.0);
|
||||||
|
let tl = [
|
||||||
|
bl[0] + nx * top_along,
|
||||||
|
bl[1] + ny * top_along,
|
||||||
|
bl[2] + nz * top_along,
|
||||||
|
];
|
||||||
|
let tr = [
|
||||||
|
br[0] + nx * top_along,
|
||||||
|
br[1] + ny * top_along,
|
||||||
|
br[2] + nz * top_along,
|
||||||
|
];
|
||||||
|
let lbl = [
|
||||||
|
bl[0] + nx * bot_along,
|
||||||
|
bl[1] + ny * bot_along,
|
||||||
|
bl[2] + nz * bot_along,
|
||||||
|
];
|
||||||
|
let lbr = [
|
||||||
|
br[0] + nx * bot_along,
|
||||||
|
br[1] + ny * bot_along,
|
||||||
|
br[2] + nz * bot_along,
|
||||||
|
];
|
||||||
|
tris.extend_from_slice(&[lbl, lbr, tr, lbl, tr, tl]);
|
||||||
|
}
|
||||||
|
tris
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,7 @@ fn tessellate_sub_local(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let text_obb_local: Option<[[f32; 3]; 4]> =
|
let text_obb_local: Option<[[f32; 3]; 4]> =
|
||||||
crate::scene::text_obb_corners_native(sub, anno_scale, mtext_lines).map(|c| {
|
crate::entities::text_support::text_obb_corners_native(sub, anno_scale, mtext_lines).map(|c| {
|
||||||
[
|
[
|
||||||
[(c[0][0] - lo_x) as f32, (c[0][1] - lo_y) as f32, (c[0][2] - lo_z) as f32],
|
[(c[0][0] - lo_x) as f32, (c[0][1] - lo_y) as f32, (c[0][2] - lo_z) as f32],
|
||||||
[(c[1][0] - lo_x) as f32, (c[1][1] - lo_y) as f32, (c[1][2] - lo_z) as f32],
|
[(c[1][0] - lo_x) as f32, (c[1][1] - lo_y) as f32, (c[1][2] - lo_z) as f32],
|
||||||
|
|
@ -941,7 +941,7 @@ struct ExpandCtx<'a> {
|
||||||
/// Fade `color` toward `bg` by 50%, preserving alpha. Used to mark xref
|
/// Fade `color` toward `bg` by 50%, preserving alpha. Used to mark xref
|
||||||
/// geometry — the hue stays recognizable but the contrast against the
|
/// geometry — the hue stays recognizable but the contrast against the
|
||||||
/// background drops, reading as "washed out".
|
/// background drops, reading as "washed out".
|
||||||
pub(super) fn fade_toward_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
pub(crate) fn fade_toward_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
||||||
const T: f32 = 0.5;
|
const T: f32 = 0.5;
|
||||||
[
|
[
|
||||||
color[0] * (1.0 - T) + bg[0] * T,
|
color[0] * (1.0 - T) + bg[0] * T,
|
||||||
|
|
|
||||||
575
src/scene/mod.rs
575
src/scene/mod.rs
|
|
@ -2637,8 +2637,8 @@ impl Scene {
|
||||||
let oy = oy as f32;
|
let oy = oy as f32;
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let (common, fills) = match entity {
|
let (common, fills) = match entity {
|
||||||
EntityType::LwPolyline(pl) => (&pl.common, wide_lwpolyline_fills(pl)),
|
EntityType::LwPolyline(pl) => (&pl.common, crate::entities::lwpolyline::wide_fills(pl)),
|
||||||
EntityType::Polyline2D(pl) => (&pl.common, wide_polyline2d_fills(pl)),
|
EntityType::Polyline2D(pl) => (&pl.common, crate::entities::polyline::wide_fills(pl)),
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
if fills.is_empty() {
|
if fills.is_empty() {
|
||||||
|
|
@ -5047,7 +5047,7 @@ fn tessellate_entity(
|
||||||
// the AttributeEntity instances live on the Insert itself in
|
// the AttributeEntity instances live on the Insert itself in
|
||||||
// WCS and need their own tessellation so the user sees the
|
// WCS and need their own tessellation so the user sees the
|
||||||
// values they actually filled in. See #20.
|
// values they actually filled in. See #20.
|
||||||
append_insert_attribute_wires(
|
crate::entities::insert::append_insert_attribute_wires(
|
||||||
&mut wires,
|
&mut wires,
|
||||||
document,
|
document,
|
||||||
ins,
|
ins,
|
||||||
|
|
@ -5119,7 +5119,7 @@ fn tessellate_entity(
|
||||||
wires
|
wires
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
append_insert_attribute_wires(
|
crate::entities::insert::append_insert_attribute_wires(
|
||||||
&mut wires,
|
&mut wires,
|
||||||
document,
|
document,
|
||||||
ins,
|
ins,
|
||||||
|
|
@ -5176,7 +5176,7 @@ fn tessellate_entity(
|
||||||
_ => 1,
|
_ => 1,
|
||||||
};
|
};
|
||||||
if h_px < 1.0 {
|
if h_px < 1.0 {
|
||||||
let pts = text_baseline_points(e, anno_scale, world_offset, n_lines);
|
let pts = crate::entities::text_support::text_baseline_points(e, anno_scale, world_offset, n_lines);
|
||||||
if pts.len() < 2 {
|
if pts.len() < 2 {
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
|
|
@ -5218,7 +5218,7 @@ fn tessellate_entity(
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
if h_px < 5.0 && aabb != WireModel::UNBOUNDED_AABB {
|
if h_px < 5.0 && aabb != WireModel::UNBOUNDED_AABB {
|
||||||
let fill_tris = text_greek_obb_tris(e, anno_scale, world_offset, n_lines);
|
let fill_tris = crate::entities::text_support::text_greek_obb_tris(e, anno_scale, world_offset, n_lines);
|
||||||
if fill_tris.is_empty() {
|
if fill_tris.is_empty() {
|
||||||
return vec![lod_stub_wire(
|
return vec![lod_stub_wire(
|
||||||
h.value().to_string(),
|
h.value().to_string(),
|
||||||
|
|
@ -5309,208 +5309,6 @@ fn tessellate_entity(
|
||||||
/// (from `text_support::mtext_line_count`). Without it, MText's OBB
|
/// (from `text_support::mtext_line_count`). Without it, MText's OBB
|
||||||
/// height collapses to a single line when the file omits `rectangle_height`,
|
/// height collapses to a single line when the file omits `rectangle_height`,
|
||||||
/// which makes downstream per-line LOD math degenerate.
|
/// which makes downstream per-line LOD math degenerate.
|
||||||
pub(crate) fn text_obb_corners_native(
|
|
||||||
e: &EntityType,
|
|
||||||
anno_scale: f32,
|
|
||||||
mtext_lines_override: Option<usize>,
|
|
||||||
) -> Option<[[f64; 3]; 4]> {
|
|
||||||
use acadrust::entities::attribute_definition::{
|
|
||||||
HorizontalAlignment as AttrHA, VerticalAlignment as AttrVA,
|
|
||||||
};
|
|
||||||
use acadrust::entities::{AttachmentPoint, TextHorizontalAlignment, TextVerticalAlignment};
|
|
||||||
|
|
||||||
let anno = anno_scale as f64;
|
|
||||||
|
|
||||||
// Map Attribute alignment enums to the same h/v anchor fractions used by
|
|
||||||
// Text. Kept local because the Attribute enum is distinct from Text's.
|
|
||||||
let attr_h_anchor = |ha: AttrHA| -> f64 {
|
|
||||||
match ha {
|
|
||||||
AttrHA::Left => 0.0,
|
|
||||||
AttrHA::Center | AttrHA::Middle | AttrHA::Aligned | AttrHA::Fit => 0.5,
|
|
||||||
AttrHA::Right => 1.0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let attr_v_anchor = |va: AttrVA| -> f64 {
|
|
||||||
match va {
|
|
||||||
AttrVA::Baseline | AttrVA::Bottom => 0.0,
|
|
||||||
AttrVA::Middle => 0.5,
|
|
||||||
AttrVA::Top => 1.0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let attr_use_align_pt = |ha: AttrHA, va: AttrVA| -> bool {
|
|
||||||
!matches!((ha, va), (AttrHA::Left, AttrVA::Baseline))
|
|
||||||
};
|
|
||||||
|
|
||||||
let (ix, iy, iz, w, h, rot, h_anchor, v_anchor) = match e {
|
|
||||||
EntityType::Text(t) => {
|
|
||||||
let h_world = t.height * anno;
|
|
||||||
let w_factor = if t.width_factor > 0.0 { t.width_factor } else { 1.0 };
|
|
||||||
let n = t.value.chars().count().max(1) as f64;
|
|
||||||
// Approximate glyph width: AutoCAD's stroke fonts average ~0.6 em.
|
|
||||||
let w_world = n * h_world * w_factor * 0.6;
|
|
||||||
let h_anchor = match t.horizontal_alignment {
|
|
||||||
TextHorizontalAlignment::Left => 0.0,
|
|
||||||
TextHorizontalAlignment::Center
|
|
||||||
| TextHorizontalAlignment::Middle
|
|
||||||
| TextHorizontalAlignment::Aligned
|
|
||||||
| TextHorizontalAlignment::Fit => 0.5,
|
|
||||||
TextHorizontalAlignment::Right => 1.0,
|
|
||||||
};
|
|
||||||
let v_anchor = match t.vertical_alignment {
|
|
||||||
TextVerticalAlignment::Baseline | TextVerticalAlignment::Bottom => 0.0,
|
|
||||||
TextVerticalAlignment::Middle => 0.5,
|
|
||||||
TextVerticalAlignment::Top => 1.0,
|
|
||||||
};
|
|
||||||
// AutoCAD writes `alignment_point` (DXF 11) whenever the text
|
|
||||||
// isn't simple Left+Baseline; in that case it — not the
|
|
||||||
// insertion_point — is the anchor the alignment fractions map to.
|
|
||||||
let use_alignment_pt = !matches!(
|
|
||||||
(t.horizontal_alignment, t.vertical_alignment),
|
|
||||||
(
|
|
||||||
TextHorizontalAlignment::Left,
|
|
||||||
TextVerticalAlignment::Baseline,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let anchor = match (use_alignment_pt, t.alignment_point) {
|
|
||||||
(true, Some(p)) => p,
|
|
||||||
_ => t.insertion_point,
|
|
||||||
};
|
|
||||||
(
|
|
||||||
anchor.x,
|
|
||||||
anchor.y,
|
|
||||||
anchor.z,
|
|
||||||
w_world,
|
|
||||||
h_world,
|
|
||||||
t.rotation,
|
|
||||||
h_anchor,
|
|
||||||
v_anchor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EntityType::AttributeDefinition(a) => {
|
|
||||||
let h_world = a.height * anno;
|
|
||||||
let w_factor = if a.width_factor > 0.0 { a.width_factor } else { 1.0 };
|
|
||||||
// Render the tag in preview when no default; matches `attribute.rs`.
|
|
||||||
let display = if a.default_value.is_empty() { &a.tag } else { &a.default_value };
|
|
||||||
let n = display.chars().count().max(1) as f64;
|
|
||||||
let w_world = n * h_world * w_factor * 0.6;
|
|
||||||
let h_anchor = attr_h_anchor(a.horizontal_alignment);
|
|
||||||
let v_anchor = attr_v_anchor(a.vertical_alignment);
|
|
||||||
let anchor = if attr_use_align_pt(a.horizontal_alignment, a.vertical_alignment) {
|
|
||||||
a.alignment_point
|
|
||||||
} else {
|
|
||||||
a.insertion_point
|
|
||||||
};
|
|
||||||
(
|
|
||||||
anchor.x, anchor.y, anchor.z, w_world, h_world, a.rotation, h_anchor, v_anchor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EntityType::AttributeEntity(a) => {
|
|
||||||
let h_world = a.height * anno;
|
|
||||||
let w_factor = if a.width_factor > 0.0 { a.width_factor } else { 1.0 };
|
|
||||||
let n = a.value.chars().count().max(1) as f64;
|
|
||||||
let w_world = n * h_world * w_factor * 0.6;
|
|
||||||
let h_anchor = attr_h_anchor(a.horizontal_alignment);
|
|
||||||
let v_anchor = attr_v_anchor(a.vertical_alignment);
|
|
||||||
let anchor = if attr_use_align_pt(a.horizontal_alignment, a.vertical_alignment) {
|
|
||||||
a.alignment_point
|
|
||||||
} else {
|
|
||||||
a.insertion_point
|
|
||||||
};
|
|
||||||
(
|
|
||||||
anchor.x, anchor.y, anchor.z, w_world, h_world, a.rotation, h_anchor, v_anchor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EntityType::Tolerance(t) => {
|
|
||||||
// Approximate: a feature control frame is roughly 1 line tall;
|
|
||||||
// width comes from char-count of the (already-stripped) text.
|
|
||||||
// GD&T symbols all advance one cell, so plain char count is close.
|
|
||||||
let raw_h = if t.text_height > 0.0 { t.text_height } else { 2.5 };
|
|
||||||
let h_world = raw_h * anno;
|
|
||||||
// Each cell ≈ 1.4 × height (matches `tolerance.rs` min_cell_w).
|
|
||||||
let n = t.text.chars().filter(|c| *c != '\n').count().max(1) as f64;
|
|
||||||
let w_world = h_world * 1.4 * n * 0.5; // rough — fine for LOD greek
|
|
||||||
// direction encodes rotation as a vector.
|
|
||||||
let rot = (t.direction.y).atan2(t.direction.x);
|
|
||||||
(
|
|
||||||
t.insertion_point.x,
|
|
||||||
t.insertion_point.y,
|
|
||||||
t.insertion_point.z,
|
|
||||||
w_world,
|
|
||||||
h_world * 1.5, // frame is taller than glyph cap by ~0.5 h
|
|
||||||
rot,
|
|
||||||
0.0, // anchored at insertion point (bottom-left)
|
|
||||||
0.0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EntityType::MText(m) => {
|
|
||||||
let h_world = m.height * anno;
|
|
||||||
let raw_lines = (m.value.matches('\n').count() + 1) as f64;
|
|
||||||
let effective_lines = match mtext_lines_override {
|
|
||||||
Some(n) => n.max(1) as f64,
|
|
||||||
None => raw_lines,
|
|
||||||
};
|
|
||||||
let w_world = if m.rectangle_width > 0.0 {
|
|
||||||
m.rectangle_width
|
|
||||||
} else {
|
|
||||||
h_world * 8.0 * effective_lines.max(1.0)
|
|
||||||
};
|
|
||||||
// Wrap-aware override beats `rectangle_height` — the stored
|
|
||||||
// height can be stale on DWGs that were re-saved without
|
|
||||||
// updating the bounds.
|
|
||||||
let total_h = if mtext_lines_override.is_some() {
|
|
||||||
h_world * effective_lines.max(1.0) * m.line_spacing_factor.max(0.5)
|
|
||||||
} else {
|
|
||||||
m.rectangle_height.unwrap_or(
|
|
||||||
h_world * raw_lines.max(1.0) * m.line_spacing_factor.max(0.5),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// MText `attachment_point` puts `insertion_point` at one of the
|
|
||||||
// 9 corners/midpoints of the text bbox. h_anchor / v_anchor are
|
|
||||||
// fractions from (left, bottom) of the bbox.
|
|
||||||
let (h_anchor, v_anchor) = match m.attachment_point {
|
|
||||||
AttachmentPoint::TopLeft => (0.0, 1.0),
|
|
||||||
AttachmentPoint::TopCenter => (0.5, 1.0),
|
|
||||||
AttachmentPoint::TopRight => (1.0, 1.0),
|
|
||||||
AttachmentPoint::MiddleLeft => (0.0, 0.5),
|
|
||||||
AttachmentPoint::MiddleCenter => (0.5, 0.5),
|
|
||||||
AttachmentPoint::MiddleRight => (1.0, 0.5),
|
|
||||||
AttachmentPoint::BottomLeft => (0.0, 0.0),
|
|
||||||
AttachmentPoint::BottomCenter => (0.5, 0.0),
|
|
||||||
AttachmentPoint::BottomRight => (1.0, 0.0),
|
|
||||||
};
|
|
||||||
(
|
|
||||||
m.insertion_point.x,
|
|
||||||
m.insertion_point.y,
|
|
||||||
m.insertion_point.z,
|
|
||||||
w_world,
|
|
||||||
total_h,
|
|
||||||
m.rotation,
|
|
||||||
h_anchor,
|
|
||||||
v_anchor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let x0 = -h_anchor * w;
|
|
||||||
let x1 = (1.0 - h_anchor) * w;
|
|
||||||
let y0 = -v_anchor * h;
|
|
||||||
let y1 = (1.0 - v_anchor) * h;
|
|
||||||
|
|
||||||
let (s, c) = (rot.sin(), rot.cos());
|
|
||||||
let rot_pt = |lx: f64, ly: f64| -> [f64; 3] {
|
|
||||||
let rx = lx * c - ly * s;
|
|
||||||
let ry = lx * s + ly * c;
|
|
||||||
[ix + rx, iy + ry, iz]
|
|
||||||
};
|
|
||||||
|
|
||||||
Some([
|
|
||||||
rot_pt(x0, y0),
|
|
||||||
rot_pt(x1, y0),
|
|
||||||
rot_pt(x1, y1),
|
|
||||||
rot_pt(x0, y1),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a "low-LOD stub" wire for an entity that would otherwise be culled
|
/// Build a "low-LOD stub" wire for an entity that would otherwise be culled
|
||||||
/// to nothing — the entity's AABB diagonal as a 2-point segment, plus the
|
/// to nothing — the entity's AABB diagonal as a 2-point segment, plus the
|
||||||
|
|
@ -5560,213 +5358,7 @@ fn lod_stub_wire(
|
||||||
/// INSERT only stamps the geometry once, attribute text sits at the world
|
/// INSERT only stamps the geometry once, attribute text sits at the world
|
||||||
/// position recorded on each ATTRIB. See #20.
|
/// position recorded on each ATTRIB. See #20.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn append_insert_attribute_wires(
|
pub(crate) fn entity_aabb(e: &acadrust::EntityType, world_offset: [f64; 3]) -> [f32; 4] {
|
||||||
wires: &mut Vec<WireModel>,
|
|
||||||
document: &acadrust::CadDocument,
|
|
||||||
ins: &acadrust::entities::Insert,
|
|
||||||
insert_handle: Handle,
|
|
||||||
sel: bool,
|
|
||||||
ins_color: [f32; 4],
|
|
||||||
ins_pat_len: f32,
|
|
||||||
ins_pat: [f32; 8],
|
|
||||||
ins_lw_px: f32,
|
|
||||||
bg_color: [f32; 4],
|
|
||||||
is_xref: bool,
|
|
||||||
pslt_factor: f32,
|
|
||||||
anno_scale: f32,
|
|
||||||
world_offset: [f64; 3],
|
|
||||||
) {
|
|
||||||
if ins.attributes.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ATTMODE (header.attribute_visibility):
|
|
||||||
// 0 = Off — every attribute hidden
|
|
||||||
// 1 = Normal — honour per-attribute `invisible` flag (default)
|
|
||||||
// 2 = On — every attribute forced visible, ignoring its flag
|
|
||||||
let attmode = document.header.attribute_visibility;
|
|
||||||
if attmode == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for attr in &ins.attributes {
|
|
||||||
let per_attr_hidden = attr.common.invisible || attr.flags.invisible;
|
|
||||||
if attmode == 1 && per_attr_hidden {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let attr_entity = EntityType::AttributeEntity(attr.clone());
|
|
||||||
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) =
|
|
||||||
render::render_style_for_block_sub(
|
|
||||||
document,
|
|
||||||
&attr_entity,
|
|
||||||
ins_color,
|
|
||||||
ins_pat_len,
|
|
||||||
ins_pat,
|
|
||||||
ins_lw_px,
|
|
||||||
);
|
|
||||||
let sub_color = render::adapt_to_bg(sub_color, bg_color);
|
|
||||||
let sub_color = if is_xref && !sel {
|
|
||||||
block_cache::fade_toward_bg(sub_color, bg_color)
|
|
||||||
} else {
|
|
||||||
sub_color
|
|
||||||
};
|
|
||||||
let sub_aabb = entity_aabb(&attr_entity, world_offset);
|
|
||||||
let mut attr_wires = tessellate::tessellate(
|
|
||||||
document,
|
|
||||||
insert_handle,
|
|
||||||
&attr_entity,
|
|
||||||
sel,
|
|
||||||
sub_color,
|
|
||||||
sub_plen * pslt_factor,
|
|
||||||
sub_pat.map(|v| v * pslt_factor),
|
|
||||||
sub_lw_px,
|
|
||||||
world_offset,
|
|
||||||
anno_scale,
|
|
||||||
);
|
|
||||||
// Use the INSERT's handle so selection / picking groups attribute
|
|
||||||
// text with the parent insert instead of treating it as a stray text.
|
|
||||||
for w in &mut attr_wires {
|
|
||||||
w.name = insert_handle.value().to_string();
|
|
||||||
w.aci = sub_aci;
|
|
||||||
w.aabb = sub_aabb;
|
|
||||||
}
|
|
||||||
wires.extend(attr_wires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NaN-separated baseline segments — one per visible wrap line — for a
|
|
||||||
/// sub-pixel Text / MText, in world-offset-subtracted f32 space. `n_lines`
|
|
||||||
/// must be the actual rendered line count (1 for Text, the wrap-expanded
|
|
||||||
/// count for MText). Lines are emitted top → bottom along the OBB's up
|
|
||||||
/// direction, each at the bottom edge of its slab.
|
|
||||||
fn text_baseline_points(
|
|
||||||
e: &EntityType,
|
|
||||||
anno_scale: f32,
|
|
||||||
world_offset: [f64; 3],
|
|
||||||
n_lines: usize,
|
|
||||||
) -> Vec<[f32; 3]> {
|
|
||||||
let Some(corners) = text_obb_corners_native(e, anno_scale, Some(n_lines)) else {
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
let line_h = match e {
|
|
||||||
EntityType::Text(t) => (t.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::MText(m) => (m.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::AttributeDefinition(a) => (a.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::AttributeEntity(a) => (a.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::Tolerance(t) => {
|
|
||||||
let raw = if t.text_height > 0.0 { t.text_height } else { 2.5 };
|
|
||||||
(raw * anno_scale as f64) as f32
|
|
||||||
}
|
|
||||||
_ => return vec![],
|
|
||||||
};
|
|
||||||
if line_h <= 0.0 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
let n_lines = n_lines.max(1);
|
|
||||||
let [ox, oy, oz] = world_offset;
|
|
||||||
let cast = |p: [f64; 3]| -> [f32; 3] {
|
|
||||||
[(p[0] - ox) as f32, (p[1] - oy) as f32, (p[2] - oz) as f32]
|
|
||||||
};
|
|
||||||
let bl = cast(corners[0]);
|
|
||||||
let br = cast(corners[1]);
|
|
||||||
let full_tl = cast(corners[3]);
|
|
||||||
|
|
||||||
let (ux, uy, uz) = (full_tl[0] - bl[0], full_tl[1] - bl[1], full_tl[2] - bl[2]);
|
|
||||||
let ulen = (ux * ux + uy * uy + uz * uz).sqrt();
|
|
||||||
if ulen < 1e-9 {
|
|
||||||
return vec![bl, br];
|
|
||||||
}
|
|
||||||
let (nx, ny, nz) = (ux / ulen, uy / ulen, uz / ulen);
|
|
||||||
|
|
||||||
let mut pts = Vec::with_capacity(n_lines * 3);
|
|
||||||
for i in 0..n_lines {
|
|
||||||
// i = 0 is the topmost line — its bottom sits one line_h below
|
|
||||||
// the OBB top (≈ `ulen`). For n_lines > ulen/line_h the deepest
|
|
||||||
// baselines clamp to the OBB bottom.
|
|
||||||
let bot_off = ((i + 1) as f32) * line_h;
|
|
||||||
let along = (ulen - bot_off).max(0.0);
|
|
||||||
let p0 = [bl[0] + nx * along, bl[1] + ny * along, bl[2] + nz * along];
|
|
||||||
let p1 = [br[0] + nx * along, br[1] + ny * along, br[2] + nz * along];
|
|
||||||
if !pts.is_empty() {
|
|
||||||
pts.push([f32::NAN; 3]);
|
|
||||||
}
|
|
||||||
pts.extend_from_slice(&[p0, p1]);
|
|
||||||
}
|
|
||||||
pts
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filled tris for a greeked top-level Text / MText. One 2-triangle rect
|
|
||||||
/// per visible line — `n_lines` is the actual rendered line count from
|
|
||||||
/// `mtext_line_count` (1 for Text). Stacked top → bottom along the OBB's
|
|
||||||
/// up direction. The face3d pipeline skips its 0.45 dim for wires with
|
|
||||||
/// empty `points`, so these tris render at the literal text color.
|
|
||||||
fn text_greek_obb_tris(
|
|
||||||
e: &EntityType,
|
|
||||||
anno_scale: f32,
|
|
||||||
world_offset: [f64; 3],
|
|
||||||
n_lines: usize,
|
|
||||||
) -> Vec<[f32; 3]> {
|
|
||||||
let Some(corners) = text_obb_corners_native(e, anno_scale, Some(n_lines)) else {
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
let line_h = match e {
|
|
||||||
EntityType::Text(t) => (t.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::MText(m) => (m.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::AttributeDefinition(a) => (a.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::AttributeEntity(a) => (a.height * anno_scale as f64) as f32,
|
|
||||||
EntityType::Tolerance(t) => {
|
|
||||||
let raw = if t.text_height > 0.0 { t.text_height } else { 2.5 };
|
|
||||||
(raw * anno_scale as f64) as f32
|
|
||||||
}
|
|
||||||
_ => return vec![],
|
|
||||||
};
|
|
||||||
if line_h <= 0.0 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
let n_lines = n_lines.max(1);
|
|
||||||
let [ox, oy, oz] = world_offset;
|
|
||||||
let cast = |p: [f64; 3]| -> [f32; 3] {
|
|
||||||
[(p[0] - ox) as f32, (p[1] - oy) as f32, (p[2] - oz) as f32]
|
|
||||||
};
|
|
||||||
let bl = cast(corners[0]);
|
|
||||||
let br = cast(corners[1]);
|
|
||||||
let full_tl = cast(corners[3]);
|
|
||||||
|
|
||||||
let (ux, uy, uz) = (full_tl[0] - bl[0], full_tl[1] - bl[1], full_tl[2] - bl[2]);
|
|
||||||
let ulen = (ux * ux + uy * uy + uz * uz).sqrt();
|
|
||||||
if ulen < 1e-9 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
let (nx, ny, nz) = (ux / ulen, uy / ulen, uz / ulen);
|
|
||||||
|
|
||||||
let mut tris = Vec::with_capacity(n_lines * 6);
|
|
||||||
for i in 0..n_lines {
|
|
||||||
let top_along = (ulen - (i as f32) * line_h).max(0.0);
|
|
||||||
let bot_along = (ulen - ((i + 1) as f32) * line_h).max(0.0);
|
|
||||||
let tl = [
|
|
||||||
bl[0] + nx * top_along,
|
|
||||||
bl[1] + ny * top_along,
|
|
||||||
bl[2] + nz * top_along,
|
|
||||||
];
|
|
||||||
let tr = [
|
|
||||||
br[0] + nx * top_along,
|
|
||||||
br[1] + ny * top_along,
|
|
||||||
br[2] + nz * top_along,
|
|
||||||
];
|
|
||||||
let lbl = [
|
|
||||||
bl[0] + nx * bot_along,
|
|
||||||
bl[1] + ny * bot_along,
|
|
||||||
bl[2] + nz * bot_along,
|
|
||||||
];
|
|
||||||
let lbr = [
|
|
||||||
br[0] + nx * bot_along,
|
|
||||||
br[1] + ny * bot_along,
|
|
||||||
br[2] + nz * bot_along,
|
|
||||||
];
|
|
||||||
tris.extend_from_slice(&[lbl, lbr, tr, lbl, tr, tl]);
|
|
||||||
}
|
|
||||||
tris
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entity_aabb(e: &acadrust::EntityType, world_offset: [f64; 3]) -> [f32; 4] {
|
|
||||||
let bbox = e.as_entity().bounding_box();
|
let bbox = e.as_entity().bounding_box();
|
||||||
let [ox, oy, _] = world_offset;
|
let [ox, oy, _] = world_offset;
|
||||||
let min_x = (bbox.min.x - ox) as f32;
|
let min_x = (bbox.min.x - ox) as f32;
|
||||||
|
|
@ -5810,156 +5402,3 @@ fn is_unindexable_entity(e: &acadrust::EntityType) -> bool {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate solid-fill boundary polygons for each wide segment of an LWPolyline.
|
|
||||||
/// Returns one polygon per segment that has non-zero width; empty if the polyline
|
|
||||||
/// has zero `constant_width` and all vertex widths are zero.
|
|
||||||
fn wide_lwpolyline_fills(pl: &acadrust::entities::LwPolyline) -> Vec<Vec<[f32; 2]>> {
|
|
||||||
let hw_const = (pl.constant_width / 2.0) as f32;
|
|
||||||
let verts = &pl.vertices;
|
|
||||||
let n = verts.len();
|
|
||||||
if n < 2 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
let seg_count = if pl.is_closed { n } else { n - 1 };
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for i in 0..seg_count {
|
|
||||||
let v0 = &verts[i];
|
|
||||||
let v1 = &verts[(i + 1) % n];
|
|
||||||
let hw0 = if v0.start_width > 1e-9 {
|
|
||||||
v0.start_width as f32 / 2.0
|
|
||||||
} else {
|
|
||||||
hw_const
|
|
||||||
};
|
|
||||||
let hw1 = if v0.end_width > 1e-9 {
|
|
||||||
v0.end_width as f32 / 2.0
|
|
||||||
} else {
|
|
||||||
hw_const
|
|
||||||
};
|
|
||||||
if hw0 < 1e-6 && hw1 < 1e-6 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let p0 = [v0.location.x as f32, v0.location.y as f32];
|
|
||||||
let p1 = [v1.location.x as f32, v1.location.y as f32];
|
|
||||||
if let Some(poly) = polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) {
|
|
||||||
out.push(poly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate solid-fill boundary polygons for each wide segment of a Polyline2D.
|
|
||||||
fn wide_polyline2d_fills(pl: &acadrust::entities::Polyline2D) -> Vec<Vec<[f32; 2]>> {
|
|
||||||
let hw_default = (pl.start_width.max(pl.end_width) / 2.0) as f32;
|
|
||||||
let verts = &pl.vertices;
|
|
||||||
let n = verts.len();
|
|
||||||
if n < 2 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
let seg_count = if pl.is_closed() { n } else { n - 1 };
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for i in 0..seg_count {
|
|
||||||
let v0 = &verts[i];
|
|
||||||
let v1 = &verts[(i + 1) % n];
|
|
||||||
let hw0 = if v0.start_width > 1e-9 {
|
|
||||||
v0.start_width as f32 / 2.0
|
|
||||||
} else {
|
|
||||||
hw_default
|
|
||||||
};
|
|
||||||
let hw1 = if v0.end_width > 1e-9 {
|
|
||||||
v0.end_width as f32 / 2.0
|
|
||||||
} else {
|
|
||||||
hw_default
|
|
||||||
};
|
|
||||||
if hw0 < 1e-6 && hw1 < 1e-6 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let p0 = [v0.location.x as f32, v0.location.y as f32];
|
|
||||||
let p1 = [v1.location.x as f32, v1.location.y as f32];
|
|
||||||
if let Some(poly) = polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) {
|
|
||||||
out.push(poly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the filled boundary polygon for one polyline segment.
|
|
||||||
/// For straight segments: a rectangle/trapezoid.
|
|
||||||
/// For arc segments: an arc band (outer arc + reversed inner arc).
|
|
||||||
/// Returns `None` if the segment is degenerate.
|
|
||||||
fn polyline_segment_fill(
|
|
||||||
p0: [f32; 2],
|
|
||||||
p1: [f32; 2],
|
|
||||||
hw0: f32,
|
|
||||||
hw1: f32,
|
|
||||||
bulge: f32,
|
|
||||||
) -> Option<Vec<[f32; 2]>> {
|
|
||||||
if bulge.abs() < 1e-9 {
|
|
||||||
// Straight segment — rectangle or trapezoid
|
|
||||||
let dx = p1[0] - p0[0];
|
|
||||||
let dy = p1[1] - p0[1];
|
|
||||||
let len = (dx * dx + dy * dy).sqrt();
|
|
||||||
if len < 1e-9 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let nx = -dy / len;
|
|
||||||
let ny = dx / len;
|
|
||||||
Some(vec![
|
|
||||||
[p0[0] + hw0 * nx, p0[1] + hw0 * ny],
|
|
||||||
[p1[0] + hw1 * nx, p1[1] + hw1 * ny],
|
|
||||||
[p1[0] - hw1 * nx, p1[1] - hw1 * ny],
|
|
||||||
[p0[0] - hw0 * nx, p0[1] - hw0 * ny],
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
// Arc segment — arc band polygon.
|
|
||||||
// Center math matches `bulge_to_arc` in modules/home/modify/explode.rs:
|
|
||||||
// r = chord * (1 + b²) / (4·|b|)
|
|
||||||
// d = r * (1 - b²) / (1 + b²) (signed: negative ⇒ major arc, center
|
|
||||||
// flips to the opposite side of chord)
|
|
||||||
// center = midpoint + sign(b) · d · left_perp(chord)
|
|
||||||
let b = bulge as f64;
|
|
||||||
let b2 = b * b;
|
|
||||||
let dx = (p1[0] - p0[0]) as f64;
|
|
||||||
let dy = (p1[1] - p0[1]) as f64;
|
|
||||||
let chord_len = (dx * dx + dy * dy).sqrt();
|
|
||||||
if chord_len < 1e-9 || b.abs() < 1e-12 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let r = chord_len * (1.0 + b2) / (4.0 * b.abs());
|
|
||||||
let d_perp = r * (1.0 - b2) / (1.0 + b2);
|
|
||||||
let mx = ((p0[0] + p1[0]) * 0.5) as f64;
|
|
||||||
let my = ((p0[1] + p1[1]) * 0.5) as f64;
|
|
||||||
let perp_x = -dy / chord_len;
|
|
||||||
let perp_y = dx / chord_len;
|
|
||||||
let sign = b.signum();
|
|
||||||
let cx = (mx + sign * d_perp * perp_x) as f32;
|
|
||||||
let cy = (my + sign * d_perp * perp_y) as f32;
|
|
||||||
let a0 = ((p0[1] - cy) as f32).atan2((p0[0] - cx) as f32);
|
|
||||||
let a1 = ((p1[1] - cy) as f32).atan2((p1[0] - cx) as f32);
|
|
||||||
let (sa, mut ea) = if bulge > 0.0 { (a0, a1) } else { (a1, a0) };
|
|
||||||
if ea < sa {
|
|
||||||
ea += std::f32::consts::TAU;
|
|
||||||
}
|
|
||||||
let span = ea - sa;
|
|
||||||
let segs = ((span.abs() / std::f32::consts::TAU) * 24.0)
|
|
||||||
.ceil()
|
|
||||||
.max(4.0) as u32;
|
|
||||||
let r = r as f32;
|
|
||||||
let r_outer = |t: f32| r + (hw0 + (hw1 - hw0) * t);
|
|
||||||
let r_inner = |t: f32| (r - (hw0 + (hw1 - hw0) * t)).max(0.0);
|
|
||||||
let mut boundary = Vec::with_capacity((segs as usize + 1) * 2);
|
|
||||||
let inv = 1.0 / segs as f32;
|
|
||||||
for j in 0..=segs {
|
|
||||||
let t = j as f32 * inv;
|
|
||||||
let ang = sa + span * t;
|
|
||||||
let ro = r_outer(t);
|
|
||||||
boundary.push([cx + ro * ang.cos(), cy + ro * ang.sin()]);
|
|
||||||
}
|
|
||||||
for j in (0..=segs).rev() {
|
|
||||||
let t = j as f32 * inv;
|
|
||||||
let ang = sa + span * t;
|
|
||||||
let ri = r_inner(t);
|
|
||||||
boundary.push([cx + ri * ang.cos(), cy + ri * ang.sin()]);
|
|
||||||
}
|
|
||||||
Some(boundary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ pub(super) fn render_style_for(
|
||||||
/// Like `render_style_for` but resolves ByBlock properties by inheriting from
|
/// Like `render_style_for` but resolves ByBlock properties by inheriting from
|
||||||
/// the INSERT entity's already-resolved style. Call this for exploded block
|
/// the INSERT entity's already-resolved style. Call this for exploded block
|
||||||
/// sub-entities so that ByBlock color/linetype/lineweight propagate correctly.
|
/// sub-entities so that ByBlock color/linetype/lineweight propagate correctly.
|
||||||
pub(super) fn render_style_for_block_sub(
|
pub(crate) fn render_style_for_block_sub(
|
||||||
document: &CadDocument,
|
document: &CadDocument,
|
||||||
e: &EntityType,
|
e: &EntityType,
|
||||||
insert_color: [f32; 4],
|
insert_color: [f32; 4],
|
||||||
|
|
@ -314,7 +314,7 @@ pub(super) fn render_style_for_block_sub(
|
||||||
/// Adapt white→black or black→white based on background luminance.
|
/// Adapt white→black or black→white based on background luminance.
|
||||||
/// White entities on light backgrounds become black, black entities on dark
|
/// White entities on light backgrounds become black, black entities on dark
|
||||||
/// backgrounds become white. All other colors pass through unchanged.
|
/// backgrounds become white. All other colors pass through unchanged.
|
||||||
pub(super) fn adapt_to_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
pub(crate) fn adapt_to_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
||||||
let lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
|
let lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
|
||||||
let is_white = color[0] > 0.95 && color[1] > 0.95 && color[2] > 0.95;
|
let is_white = color[0] > 0.95 && color[1] > 0.95 && color[2] > 0.95;
|
||||||
let is_black = color[0] < 0.05 && color[1] < 0.05 && color[2] < 0.05;
|
let is_black = color[0] < 0.05 && color[1] < 0.05 && color[2] < 0.05;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue