forked from 1b-agent/opencadstudio-upstream
feat(mline): render multilines from their MLINESTYLE + complex linetypes
Multilines were drawn with a fixed ±scale/2 two-line guess in the entity colour, ignoring the MLINESTYLE entirely, so a drawing's custom style (per- element offsets, colours and linetypes) was lost — a red/yellow multiline rendered as a white box. - mline.rs: resolve the MLINESTYLE (by style_handle, then name) and build one parallel line per element from its offset x scale + justification shift. - tessellate.rs: special-case MLINE (like Leader) to emit one WireModel per element in the element's own colour, CPU-dashing every element through apply_along so the parallel lines stay in phase. - linetypes.rs: render a drawing's OWN complex linetypes (embedded text/shape), document definition first and the bundled .lin catalog only as a fallback, so a file-defined text linetype like PLYN shows its text. - Run the dash/glyph layout in a local frame (f64 origin subtracted, re-split to double-single after) so spacing stays precise at UTM coordinates, and pass tracking = 1.0 so embedded text keeps its letter spacing. Cargo.lock pins acadrust to the MLINE->MLINESTYLE handle-link reader fix. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
e85e2aeb78
commit
0e18b5bb33
6 files changed
+454
-75
No files matched your search
+107
-1
@@ -8,7 +8,7 @@
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use acadrust::tables::linetype::{LineType, LineTypeElement};
|
||||
use acadrust::tables::linetype::{LineType, LineTypeComplexContent, LineTypeElement};
|
||||
use acadrust::{CadDocument, TableEntry};
|
||||
|
||||
// ── Complex linetype types ────────────────────────────────────────────────
|
||||
@@ -69,6 +69,112 @@ pub fn complex_lt(name: &str) -> Option<&'static ComplexLt> {
|
||||
.get(&name.to_ascii_uppercase())
|
||||
}
|
||||
|
||||
/// Build the full segment list — dashes, spaces, dots **and** embedded
|
||||
/// text / shape — for a linetype defined in the loaded **document**, whether it
|
||||
/// is simple or complex.
|
||||
///
|
||||
/// Returns `None` when the linetype is missing or continuous (no pattern). Used
|
||||
/// by the MLINE renderer: every element is CPU-dashed by the same walker so the
|
||||
/// parallel lines (and any embedded text) stay in phase with each other. Ordinary
|
||||
/// entities keep the cheaper shader dash for simple linetypes; see
|
||||
/// [`document_complex_lt`] / [`resolve_complex_lt`].
|
||||
pub fn document_lt_segments(document: &CadDocument, name: &str) -> Option<ComplexLt> {
|
||||
let lt = document
|
||||
.line_types
|
||||
.iter()
|
||||
.find(|l| l.name.eq_ignore_ascii_case(name))?;
|
||||
|
||||
let mut segments: Vec<LtSegment> = Vec::with_capacity(lt.elements.len() + 1);
|
||||
let mut has_length = false;
|
||||
for e in <.elements {
|
||||
// Emit the dash / space for this element FIRST, then any embedded
|
||||
// text / shape — the pen advances across the element's length before the
|
||||
// glyph is placed, so the text lands at the element's *end* (inside the
|
||||
// gap), matching the `.lin` token order (`dash, -space, ["TEXT"], …`) and
|
||||
// AutoCAD's placement. Putting the text first drops it onto the preceding
|
||||
// dash, so the dash strikes through the glyphs.
|
||||
let len = e.length as f32;
|
||||
if len > 1e-9 {
|
||||
segments.push(LtSegment::Dash(len));
|
||||
has_length = true;
|
||||
} else if len < -1e-9 {
|
||||
segments.push(LtSegment::Space(-len));
|
||||
has_length = true;
|
||||
} else if e.complex.is_none() {
|
||||
// A zero-length *non-complex* element is a dot; a zero-length complex
|
||||
// element only contributes its text/shape (pushed below).
|
||||
segments.push(LtSegment::Dot);
|
||||
has_length = true;
|
||||
}
|
||||
|
||||
if let Some(c) = &e.complex {
|
||||
match &c.content {
|
||||
LineTypeComplexContent::Text { text } => {
|
||||
let style_name = document
|
||||
.text_styles
|
||||
.iter()
|
||||
.find(|s| s.handle == c.style_handle)
|
||||
.map(|s| s.name.as_str())
|
||||
.unwrap_or("");
|
||||
let font = crate::entities::text_support::resolve_text_style(
|
||||
style_name, document,
|
||||
)
|
||||
.font_name;
|
||||
let scale = c.scale as f32;
|
||||
segments.push(LtSegment::Text {
|
||||
text: text.clone(),
|
||||
style: font,
|
||||
x: c.offset[0] as f32,
|
||||
y: c.offset[1] as f32,
|
||||
scale: if scale.abs() < 1e-6 { 1.0 } else { scale },
|
||||
rot_deg: c.rotation as f32,
|
||||
});
|
||||
}
|
||||
// Embedded SHAPE glyphs need a .shp set we don't ship; skip the
|
||||
// draw (matches the bundled-catalog behaviour).
|
||||
LineTypeComplexContent::Shape { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No dash/space/dot means "continuous" — let the caller draw a solid line.
|
||||
if !has_length {
|
||||
return None;
|
||||
}
|
||||
Some(ComplexLt { segments })
|
||||
}
|
||||
|
||||
/// Complex (embedded text/shape) segments for a **document** linetype, or `None`
|
||||
/// when it is simple (dash-only) — simple dashes are handled by the ordinary
|
||||
/// `resolve_pattern` shader path, so ordinary entities don't pay the CPU-dash
|
||||
/// cost. This is the gate used by [`resolve_complex_lt`].
|
||||
pub fn document_complex_lt(document: &CadDocument, name: &str) -> Option<ComplexLt> {
|
||||
let lt = document
|
||||
.line_types
|
||||
.iter()
|
||||
.find(|l| l.name.eq_ignore_ascii_case(name))?;
|
||||
if !lt.elements.iter().any(|e| e.complex.is_some()) {
|
||||
return None;
|
||||
}
|
||||
document_lt_segments(document, name)
|
||||
}
|
||||
|
||||
/// Resolve a complex linetype honouring "**document first, bundled catalog
|
||||
/// second**": if the drawing defines the linetype, its own definition wins
|
||||
/// (embedded text/shape, or `None` when the drawing's version is simple); only a
|
||||
/// linetype absent from the drawing falls back to the bundled `.lin` catalog.
|
||||
pub fn resolve_complex_lt(document: &CadDocument, name: &str) -> Option<ComplexLt> {
|
||||
let in_document = document
|
||||
.line_types
|
||||
.iter()
|
||||
.any(|l| l.name.eq_ignore_ascii_case(name));
|
||||
if in_document {
|
||||
document_complex_lt(document, name)
|
||||
} else {
|
||||
complex_lt(name).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
const LIN_SOURCE: &str = include_str!("../../assets/linetypes/OpenCADStudio.lin");
|
||||
|
||||
// ── Pattern art extraction ────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user