forked from 1b-agent/opencadstudio-upstream
1 parent
c3df935905
commit
20c6d419e2
6 files changed
+393
-101
No files matched your search
+14
-1
@@ -611,7 +611,20 @@ impl OpenCADStudio {
|
|||||||
// Dropdown options (names must match the records exactly so the
|
// Dropdown options (names must match the records exactly so the
|
||||||
// selection can be resolved back to a handle on the update side).
|
// selection can be resolved back to a handle on the update side).
|
||||||
let mut block_opts: Vec<String> = vec!["Default".to_string()];
|
let mut block_opts: Vec<String> = vec!["Default".to_string()];
|
||||||
block_opts.extend(doc.block_records.iter().map(|b| b.name.clone()));
|
block_opts.extend(
|
||||||
|
doc.block_records
|
||||||
|
.iter()
|
||||||
|
.filter(|b| {
|
||||||
|
!b.is_layout()
|
||||||
|
&& !b.is_model_space()
|
||||||
|
&& !b.is_paper_space()
|
||||||
|
&& !b.flags.is_xref
|
||||||
|
&& !b.flags.is_xref_overlay
|
||||||
|
&& !b.flags.is_external
|
||||||
|
&& !b.name.starts_with('*')
|
||||||
|
})
|
||||||
|
.map(|b| b.name.clone()),
|
||||||
|
);
|
||||||
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
|
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
|
||||||
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
|
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
|
||||||
let blk_name = |h: acadrust::types::Handle| -> String {
|
let blk_name = |h: acadrust::types::Handle| -> String {
|
||||||
|
|||||||
@@ -1388,6 +1388,27 @@ fn apply_dimension_breaks(
|
|||||||
*lines = output;
|
*lines = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn uses_custom_arrow_blocks(document: &CadDocument, dim: &Dimension) -> bool {
|
||||||
|
let style_name = &dim.base().style_name;
|
||||||
|
let Some(style) = document.dim_styles.iter().find(|style| {
|
||||||
|
style.name.eq_ignore_ascii_case(style_name)
|
||||||
|
|| (style_name.trim().is_empty() && style.name.eq_ignore_ascii_case("Standard"))
|
||||||
|
}) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if style.dimtsz > 1e-9 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let is_custom = |handle| {
|
||||||
|
crate::scene::convert::tessellate::arrow_block_is_custom(document, handle)
|
||||||
|
};
|
||||||
|
if style.dimsah {
|
||||||
|
is_custom(style.dimblk1) || is_custom(style.dimblk2)
|
||||||
|
} else {
|
||||||
|
is_custom(style.dimblk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait DimensionTess {
|
pub trait DimensionTess {
|
||||||
fn tessellate(
|
fn tessellate(
|
||||||
&self,
|
&self,
|
||||||
@@ -1528,7 +1549,7 @@ fn tessellate_dimension_inner(
|
|||||||
let t = ArrowKind::Tick {
|
let t = ArrowKind::Tick {
|
||||||
size: (dimtsz_raw as f32).max(0.001),
|
size: (dimtsz_raw as f32).max(0.001),
|
||||||
};
|
};
|
||||||
(t, t)
|
(t.clone(), t)
|
||||||
} else if let Some(s) = style {
|
} else if let Some(s) = style {
|
||||||
if dimsah {
|
if dimsah {
|
||||||
(
|
(
|
||||||
@@ -1537,7 +1558,7 @@ fn tessellate_dimension_inner(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let a = arrow_from_block(document, s.dimblk, dimasz);
|
let a = arrow_from_block(document, s.dimblk, dimasz);
|
||||||
(a, a)
|
(a.clone(), a)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let a = ArrowKind::Triangle {
|
let a = ArrowKind::Triangle {
|
||||||
@@ -1545,7 +1566,7 @@ fn tessellate_dimension_inner(
|
|||||||
filled: true,
|
filled: true,
|
||||||
size_mul: 1.0,
|
size_mul: 1.0,
|
||||||
};
|
};
|
||||||
(a, a)
|
(a.clone(), a)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Text box (local space) so the dim line can be broken where the text
|
// Text box (local space) so the dim line can be broken where the text
|
||||||
|
|||||||
@@ -360,13 +360,13 @@ fn dim_terminator(
|
|||||||
s.common.handle = Handle::NULL;
|
s.common.handle = Handle::NULL;
|
||||||
out.push(EntityType::Solid(s));
|
out.push(EntityType::Solid(s));
|
||||||
};
|
};
|
||||||
match *arrow {
|
match arrow {
|
||||||
A::None => {}
|
A::None => {}
|
||||||
A::Triangle { size, filled, size_mul } => {
|
A::Triangle { size, filled, size_mul } => {
|
||||||
let size = (size * size_mul) as f64;
|
let size = (*size * *size_mul) as f64;
|
||||||
let hw = size / 6.0;
|
let hw = size / 6.0;
|
||||||
let (l, r) = (pt(size, hw), pt(size, -hw));
|
let (l, r) = (pt(size, hw), pt(size, -hw));
|
||||||
if filled {
|
if *filled {
|
||||||
tri(tip, l, r, &mut out);
|
tri(tip, l, r, &mut out);
|
||||||
} else {
|
} else {
|
||||||
out.push(dim_seg(tip, l, common));
|
out.push(dim_seg(tip, l, common));
|
||||||
@@ -375,7 +375,7 @@ fn dim_terminator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
A::Tick { size } => {
|
A::Tick { size } => {
|
||||||
let s = size as f64;
|
let s = *size as f64;
|
||||||
let (ox, oy) = (dx + px, dy + py);
|
let (ox, oy) = (dx + px, dy + py);
|
||||||
let m = (ox * ox + oy * oy).sqrt().max(1e-9);
|
let m = (ox * ox + oy * oy).sqrt().max(1e-9);
|
||||||
let (ox, oy) = (ox / m * s, oy / m * s);
|
let (ox, oy) = (ox / m * s, oy / m * s);
|
||||||
@@ -386,42 +386,72 @@ fn dim_terminator(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
A::Open { size, half_angle } => {
|
A::Open { size, half_angle } => {
|
||||||
let size = size as f64;
|
let size = *size as f64;
|
||||||
let hw = size * (half_angle as f64).tan();
|
let hw = size * (*half_angle as f64).tan();
|
||||||
out.push(dim_seg(tip, pt(size, hw), common));
|
out.push(dim_seg(tip, pt(size, hw), common));
|
||||||
out.push(dim_seg(tip, pt(size, -hw), common));
|
out.push(dim_seg(tip, pt(size, -hw), common));
|
||||||
}
|
}
|
||||||
A::Dot { size, filled } => {
|
A::Dot { size, filled } => {
|
||||||
let r = size as f64 * 0.5;
|
let r = *size as f64 * 0.5;
|
||||||
terminator_circle(tip, r, filled, common, &mut out);
|
terminator_circle(tip, r, *filled, common, &mut out);
|
||||||
}
|
}
|
||||||
A::Origin { size } => {
|
A::Origin { size } => {
|
||||||
terminator_circle(tip, size as f64 * 0.25, true, common, &mut out);
|
terminator_circle(tip, *size as f64 * 0.25, true, common, &mut out);
|
||||||
let half = size as f64 * 0.5;
|
let half = *size as f64 * 0.5;
|
||||||
out.push(dim_seg(pt(0.0, -half), pt(0.0, half), common));
|
out.push(dim_seg(pt(0.0, -half), pt(0.0, half), common));
|
||||||
}
|
}
|
||||||
A::Box_ { size, filled } => {
|
A::Box_ { size, filled } => {
|
||||||
let h = size as f64 * 0.5;
|
let h = *size as f64 * 0.5;
|
||||||
let (p1, p2, p3, p4) = (pt(-h, -h), pt(h, -h), pt(h, h), pt(-h, h));
|
let (p1, p2, p3, p4) = (pt(-h, -h), pt(h, -h), pt(h, h), pt(-h, h));
|
||||||
out.push(dim_seg(p1, p2, common));
|
out.push(dim_seg(p1, p2, common));
|
||||||
out.push(dim_seg(p2, p3, common));
|
out.push(dim_seg(p2, p3, common));
|
||||||
out.push(dim_seg(p3, p4, common));
|
out.push(dim_seg(p3, p4, common));
|
||||||
out.push(dim_seg(p4, p1, common));
|
out.push(dim_seg(p4, p1, common));
|
||||||
if filled {
|
if *filled {
|
||||||
tri(p1, p2, p3, &mut out);
|
tri(p1, p2, p3, &mut out);
|
||||||
tri(p1, p3, p4, &mut out);
|
tri(p1, p3, p4, &mut out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
A::Datum { size, filled } => {
|
A::Datum { size, filled } => {
|
||||||
let half = size as f64 * 0.5;
|
let half = *size as f64 * 0.5;
|
||||||
let (ba, bb, apex) = (pt(0.0, half), pt(0.0, -half), pt(size as f64, 0.0));
|
let (ba, bb, apex) = (pt(0.0, half), pt(0.0, -half), pt(*size as f64, 0.0));
|
||||||
out.push(dim_seg(ba, apex, common));
|
out.push(dim_seg(ba, apex, common));
|
||||||
out.push(dim_seg(apex, bb, common));
|
out.push(dim_seg(apex, bb, common));
|
||||||
out.push(dim_seg(bb, ba, common));
|
out.push(dim_seg(bb, ba, common));
|
||||||
if filled {
|
if *filled {
|
||||||
tri(ba, apex, bb, &mut out);
|
tri(ba, apex, bb, &mut out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
A::Custom { size, lines, fill } => {
|
||||||
|
let custom_pt = |point: &[f32; 3]| {
|
||||||
|
let mut placed = pt(
|
||||||
|
-(point[0] as f64) * *size as f64,
|
||||||
|
-(point[1] as f64) * *size as f64,
|
||||||
|
);
|
||||||
|
placed.z += point[2] as f64 * *size as f64;
|
||||||
|
placed
|
||||||
|
};
|
||||||
|
let mut previous = None;
|
||||||
|
for point in lines {
|
||||||
|
if point[0].is_nan() {
|
||||||
|
previous = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let current = custom_pt(point);
|
||||||
|
if let Some(previous) = previous {
|
||||||
|
out.push(dim_seg(previous, current, common));
|
||||||
|
}
|
||||||
|
previous = Some(current);
|
||||||
|
}
|
||||||
|
for triangle in fill.chunks_exact(3) {
|
||||||
|
tri(
|
||||||
|
custom_pt(&triangle[0]),
|
||||||
|
custom_pt(&triangle[1]),
|
||||||
|
custom_pt(&triangle[2]),
|
||||||
|
&mut out,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
@@ -576,17 +606,17 @@ fn dim_metrics(dim: &Dimension, doc: &CadDocument) -> DimMetrics {
|
|||||||
// else closed-filled.
|
// else closed-filled.
|
||||||
let (arrow1, arrow2) = if dimtsz > 1e-9 {
|
let (arrow1, arrow2) = if dimtsz > 1e-9 {
|
||||||
let t = ArrowKind::Tick { size: dimtsz as f32 };
|
let t = ArrowKind::Tick { size: dimtsz as f32 };
|
||||||
(t, t)
|
(t.clone(), t)
|
||||||
} else if let Some(s) = style {
|
} else if let Some(s) = style {
|
||||||
if s.dimsah {
|
if s.dimsah {
|
||||||
(arrow_from_block(doc, s.dimblk1, asz), arrow_from_block(doc, s.dimblk2, asz))
|
(arrow_from_block(doc, s.dimblk1, asz), arrow_from_block(doc, s.dimblk2, asz))
|
||||||
} else {
|
} else {
|
||||||
let a = arrow_from_block(doc, s.dimblk, asz);
|
let a = arrow_from_block(doc, s.dimblk, asz);
|
||||||
(a, a)
|
(a.clone(), a)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let a = ArrowKind::Triangle { size: asz, filled: true, size_mul: 1.0 };
|
let a = ArrowKind::Triangle { size: asz, filled: true, size_mul: 1.0 };
|
||||||
(a, a)
|
(a.clone(), a)
|
||||||
};
|
};
|
||||||
DimMetrics {
|
DimMetrics {
|
||||||
dimasz,
|
dimasz,
|
||||||
|
|||||||
Vendored
+4
-1
@@ -434,7 +434,10 @@ fn build_defn(
|
|||||||
// drew nothing. Expand the `*D` block's entities here as block-local
|
// drew nothing. Expand the `*D` block's entities here as block-local
|
||||||
// subs so they transform with the parent insert. (Empty block_name
|
// subs so they transform with the parent insert. (Empty block_name
|
||||||
// — a non-baked dimension — falls through to the default arm.)
|
// — a non-baked dimension — falls through to the default arm.)
|
||||||
EntityType::Dimension(dim) if !dim.base().block_name.trim().is_empty() => {
|
EntityType::Dimension(dim)
|
||||||
|
if !dim.base().block_name.trim().is_empty()
|
||||||
|
&& !crate::entities::dimension::uses_custom_arrow_blocks(doc, dim) =>
|
||||||
|
{
|
||||||
let dblk = doc
|
let dblk = doc
|
||||||
.block_records
|
.block_records
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -738,15 +738,17 @@ pub(crate) fn tessellate_entity(
|
|||||||
|
|
||||||
// ── Dimension baked-block fast path ─────────────────────────────────────
|
// ── Dimension baked-block fast path ─────────────────────────────────────
|
||||||
//
|
//
|
||||||
// AutoCAD bakes each dimension's final geometry (extension lines, dim
|
// A saved dimension may carry final geometry (extension lines, dim line,
|
||||||
// line, arrows, text MText) into a per-instance block — usually
|
// arrows and text) in a per-instance block — usually
|
||||||
// `*D<n>`, but custom names like `DIMBLOCK###-4NP` also occur. When the
|
// `*D<n>`, but custom names like `DIMBLOCK###-4NP` also occur. When the
|
||||||
// block exists we render its contents through `tessellate_entity` so
|
// block exists we render its contents through `tessellate_entity` so
|
||||||
// sub-Text/MText get the standard baseline/greek/full LOD ladder, and
|
// sub-Text/MText get the standard baseline/greek/full LOD ladder, and
|
||||||
// DIMTXT × DIMSCALE isn't re-applied on already-baked geometry.
|
// DIMTXT × DIMSCALE isn't re-applied on already-baked geometry.
|
||||||
if let EntityType::Dimension(dim) = e {
|
if let EntityType::Dimension(dim) = e {
|
||||||
let block_name = &dim.base().block_name;
|
let block_name = &dim.base().block_name;
|
||||||
if !block_name.trim().is_empty() {
|
if !block_name.trim().is_empty()
|
||||||
|
&& !crate::entities::dimension::uses_custom_arrow_blocks(document, dim)
|
||||||
|
{
|
||||||
if let Some(br) = document
|
if let Some(br) = document
|
||||||
.block_records
|
.block_records
|
||||||
.iter()
|
.iter()
|
||||||
@@ -773,8 +775,8 @@ pub(crate) fn tessellate_entity(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// A dimension's definition points are baked into the
|
// A dimension's definition points are baked into the
|
||||||
// block as POINTs on the Defpoints layer. AutoCAD never
|
// block as POINTs on the Defpoints layer. They are grip
|
||||||
// draws them as PDMODE glyphs — they're grip markers, not
|
// markers rather than PDMODE display geometry, so
|
||||||
// geometry — so rendering them adds a stray tick at each
|
// geometry — so rendering them adds a stray tick at each
|
||||||
// measured point that makes the extension lines look like
|
// measured point that makes the extension lines look like
|
||||||
// they run past the geometry. Skip them.
|
// they run past the geometry. Skip them.
|
||||||
|
|||||||
+296
-73
@@ -1503,7 +1503,7 @@ pub fn tessellate(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum ArrowKind {
|
pub(crate) enum ArrowKind {
|
||||||
None,
|
None,
|
||||||
Triangle { size: f32, filled: bool, size_mul: f32 },
|
Triangle { size: f32, filled: bool, size_mul: f32 },
|
||||||
@@ -1513,6 +1513,11 @@ pub(crate) enum ArrowKind {
|
|||||||
Origin { size: f32 },
|
Origin { size: f32 },
|
||||||
Box_ { size: f32, filled: bool },
|
Box_ { size: f32, filled: bool },
|
||||||
Datum { size: f32, filled: bool },
|
Datum { size: f32, filled: bool },
|
||||||
|
Custom {
|
||||||
|
size: f32,
|
||||||
|
lines: Vec<[f32; 3]>,
|
||||||
|
fill: Vec<[f32; 3]>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn arrow_from_block(
|
pub(crate) fn arrow_from_block(
|
||||||
@@ -1520,100 +1525,288 @@ pub(crate) fn arrow_from_block(
|
|||||||
handle: acadrust::types::Handle,
|
handle: acadrust::types::Handle,
|
||||||
dimasz: f32,
|
dimasz: f32,
|
||||||
) -> ArrowKind {
|
) -> ArrowKind {
|
||||||
let name = if handle.is_null() {
|
if handle.is_null() {
|
||||||
None
|
return arrow_from_block_name(None, dimasz);
|
||||||
} else {
|
}
|
||||||
doc.block_records
|
let Some(record) = doc.block_records.iter().find(|b| b.handle == handle) else {
|
||||||
.iter()
|
return arrow_from_block_name(None, dimasz);
|
||||||
.find(|b| b.handle == handle)
|
|
||||||
.map(|b| b.name.as_str())
|
|
||||||
};
|
};
|
||||||
arrow_from_block_name(name, dimasz)
|
if let Some(arrow) = builtin_arrow_from_block_name(&record.name, dimasz) {
|
||||||
|
return arrow;
|
||||||
|
}
|
||||||
|
custom_arrow_from_block(doc, record, dimasz)
|
||||||
|
.unwrap_or_else(|| arrow_from_block_name(None, dimasz))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn arrow_block_is_custom(
|
||||||
|
doc: &CadDocument,
|
||||||
|
handle: acadrust::types::Handle,
|
||||||
|
) -> bool {
|
||||||
|
if handle.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
doc.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|record| record.handle == handle)
|
||||||
|
.is_some_and(|record| {
|
||||||
|
!record.entity_handles.is_empty()
|
||||||
|
&& !record.is_layout()
|
||||||
|
&& !record.is_model_space()
|
||||||
|
&& !record.is_paper_space()
|
||||||
|
&& !record.flags.is_xref
|
||||||
|
&& !record.flags.is_xref_overlay
|
||||||
|
&& !record.flags.is_external
|
||||||
|
&& builtin_arrow_from_block_name(&record.name, 1.0).is_none()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arrow_from_block_name(name: Option<&str>, dimasz: f32) -> ArrowKind {
|
fn arrow_from_block_name(name: Option<&str>, dimasz: f32) -> ArrowKind {
|
||||||
// AutoCAD's standard arrow blocks are prefixed with "_" (e.g. "_OPEN").
|
name.and_then(|name| builtin_arrow_from_block_name(name, dimasz))
|
||||||
// Strip the prefix, upper-case, and switch on canonical names. Unknown
|
.unwrap_or(ArrowKind::Triangle {
|
||||||
// / missing names default to ClosedFilled.
|
|
||||||
let n = name
|
|
||||||
.map(|s| s.trim().trim_start_matches('_').to_ascii_uppercase())
|
|
||||||
.unwrap_or_default();
|
|
||||||
match n.as_str() {
|
|
||||||
"" | "CLOSEDFILLED" => ArrowKind::Triangle {
|
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: true,
|
filled: true,
|
||||||
size_mul: 1.0,
|
size_mul: 1.0,
|
||||||
},
|
})
|
||||||
"CLOSED" | "CLOSEDBLANK" => ArrowKind::Triangle {
|
}
|
||||||
|
|
||||||
|
fn builtin_arrow_from_block_name(name: &str, dimasz: f32) -> Option<ArrowKind> {
|
||||||
|
// Built-in arrow block names may carry a leading underscore. Normalize it
|
||||||
|
// before matching the canonical names.
|
||||||
|
let n = name
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches('_')
|
||||||
|
.to_ascii_uppercase();
|
||||||
|
match n.as_str() {
|
||||||
|
"" | "CLOSEDFILLED" => Some(ArrowKind::Triangle {
|
||||||
|
size: dimasz,
|
||||||
|
filled: true,
|
||||||
|
size_mul: 1.0,
|
||||||
|
}),
|
||||||
|
"CLOSED" | "CLOSEDBLANK" => Some(ArrowKind::Triangle {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: false,
|
filled: false,
|
||||||
size_mul: 1.0,
|
size_mul: 1.0,
|
||||||
},
|
}),
|
||||||
"SMALL" => ArrowKind::Triangle {
|
"SMALL" => Some(ArrowKind::Triangle {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: true,
|
filled: true,
|
||||||
size_mul: 0.5,
|
size_mul: 0.5,
|
||||||
},
|
}),
|
||||||
"OPEN" => ArrowKind::Open {
|
"OPEN" => Some(ArrowKind::Open {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
half_angle: 9.5_f32.to_radians(),
|
half_angle: 9.5_f32.to_radians(),
|
||||||
},
|
}),
|
||||||
"OPEN30" => ArrowKind::Open {
|
"OPEN30" => Some(ArrowKind::Open {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
half_angle: 15.0_f32.to_radians(),
|
half_angle: 15.0_f32.to_radians(),
|
||||||
},
|
}),
|
||||||
"OPEN90" => ArrowKind::Open {
|
"OPEN90" => Some(ArrowKind::Open {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
half_angle: 45.0_f32.to_radians(),
|
half_angle: 45.0_f32.to_radians(),
|
||||||
},
|
}),
|
||||||
"DOT" => ArrowKind::Dot {
|
"DOT" => Some(ArrowKind::Dot {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: true,
|
filled: true,
|
||||||
},
|
}),
|
||||||
"DOTSMALL" => ArrowKind::Dot {
|
"DOTSMALL" => Some(ArrowKind::Dot {
|
||||||
size: dimasz * 0.5,
|
size: dimasz * 0.5,
|
||||||
filled: true,
|
filled: true,
|
||||||
},
|
}),
|
||||||
"DOTBLANK" => ArrowKind::Dot {
|
"DOTBLANK" => Some(ArrowKind::Dot {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: false,
|
filled: false,
|
||||||
},
|
}),
|
||||||
"DOTSMALLBLANK" => ArrowKind::Dot {
|
"DOTSMALLBLANK" => Some(ArrowKind::Dot {
|
||||||
size: dimasz * 0.5,
|
size: dimasz * 0.5,
|
||||||
filled: false,
|
filled: false,
|
||||||
},
|
}),
|
||||||
"ORIGIN" | "ORIGIN2" | "ORIGININDICATOR" | "ORIGININDICATOR2" => {
|
"ORIGIN" | "ORIGIN2" | "ORIGININDICATOR" | "ORIGININDICATOR2" => {
|
||||||
ArrowKind::Origin { size: dimasz }
|
Some(ArrowKind::Origin { size: dimasz })
|
||||||
}
|
}
|
||||||
// `ArrowKind::Tick` draws the stroke `size` to either side of the tip
|
// `ArrowKind::Tick` draws the stroke `size` to either side of the tip
|
||||||
// (total 2·size — its `size` is a half-length, matching DIMTSZ). As an
|
// (total 2·size — its `size` is a half-length, matching DIMTSZ). For
|
||||||
// arrowhead block, though, DIMASZ is the stroke's *full* length like
|
// a block-selected tick DIMASZ is the full stroke length, so halve it.
|
||||||
// every other arrowhead here, so pass half of it — otherwise the
|
"OBLIQUE" | "ARCHTICK" => Some(ArrowKind::Tick { size: dimasz * 0.5 }),
|
||||||
// oblique tick renders twice the intended DIMASZ.
|
"BOXFILLED" => Some(ArrowKind::Box_ {
|
||||||
"OBLIQUE" | "ARCHTICK" => ArrowKind::Tick { size: dimasz * 0.5 },
|
|
||||||
"BOXFILLED" => ArrowKind::Box_ {
|
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: true,
|
filled: true,
|
||||||
},
|
}),
|
||||||
"BOXBLANK" | "BOX" => ArrowKind::Box_ {
|
"BOXBLANK" | "BOX" => Some(ArrowKind::Box_ {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: false,
|
filled: false,
|
||||||
},
|
}),
|
||||||
"DATUMFILLED" | "DATUMTRIANGLEFILLED" => ArrowKind::Datum {
|
"DATUMFILLED" | "DATUMTRIANGLEFILLED" => Some(ArrowKind::Datum {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: true,
|
filled: true,
|
||||||
},
|
}),
|
||||||
"DATUMBLANK" | "DATUMTRIANGLE" => ArrowKind::Datum {
|
"DATUMBLANK" | "DATUMTRIANGLE" => Some(ArrowKind::Datum {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: false,
|
filled: false,
|
||||||
},
|
}),
|
||||||
"NONE" => ArrowKind::None,
|
"NONE" => Some(ArrowKind::None),
|
||||||
// INTEGRAL and other complex glyphs aren't reproduced here; fall through.
|
_ => None,
|
||||||
_ => ArrowKind::Triangle {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn custom_arrow_from_block(
|
||||||
|
doc: &CadDocument,
|
||||||
|
record: &acadrust::tables::BlockRecord,
|
||||||
|
dimasz: f32,
|
||||||
|
) -> Option<ArrowKind> {
|
||||||
|
if record.is_layout()
|
||||||
|
|| record.is_model_space()
|
||||||
|
|| record.is_paper_space()
|
||||||
|
|| record.flags.is_xref
|
||||||
|
|| record.flags.is_xref_overlay
|
||||||
|
|| record.flags.is_external
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let base = block_base_point(doc, record);
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
let mut fill = Vec::new();
|
||||||
|
let mut stack = vec![record.name.clone()];
|
||||||
|
for &entity_handle in &record.entity_handles {
|
||||||
|
let Some(entity) = doc.get_entity(entity_handle) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
collect_custom_arrow_entity(doc, entity, base, &mut lines, &mut fill, &mut stack, 0);
|
||||||
|
}
|
||||||
|
if lines.is_empty() && fill.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ArrowKind::Custom {
|
||||||
size: dimasz,
|
size: dimasz,
|
||||||
filled: true,
|
lines,
|
||||||
size_mul: 1.0,
|
fill,
|
||||||
},
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_base_point(
|
||||||
|
doc: &CadDocument,
|
||||||
|
record: &acadrust::tables::BlockRecord,
|
||||||
|
) -> acadrust::types::Vector3 {
|
||||||
|
doc.get_entity(record.block_entity_handle)
|
||||||
|
.and_then(|entity| match entity {
|
||||||
|
EntityType::Block(block) => Some(block.base_point),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(acadrust::types::Vector3::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_custom_arrow_entity(
|
||||||
|
doc: &CadDocument,
|
||||||
|
entity: &EntityType,
|
||||||
|
root_base: acadrust::types::Vector3,
|
||||||
|
lines: &mut Vec<[f32; 3]>,
|
||||||
|
fill: &mut Vec<[f32; 3]>,
|
||||||
|
stack: &mut Vec<String>,
|
||||||
|
depth: usize,
|
||||||
|
) {
|
||||||
|
if depth >= 32 || entity.common().invisible {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match entity {
|
||||||
|
EntityType::Block(_)
|
||||||
|
| EntityType::BlockEnd(_)
|
||||||
|
| EntityType::AttributeDefinition(_)
|
||||||
|
| EntityType::Dimension(_)
|
||||||
|
| EntityType::Leader(_)
|
||||||
|
| EntityType::MultiLeader(_) => return,
|
||||||
|
EntityType::Insert(insert) => {
|
||||||
|
if stack.iter().any(|name| name.eq_ignore_ascii_case(&insert.block_name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(record) = doc.block_records.get(&insert.block_name) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let nested_base = block_base_point(doc, record);
|
||||||
|
let transform = insert.get_transform();
|
||||||
|
let transformed_zero = transform.apply(acadrust::types::Vector3::ZERO);
|
||||||
|
let transformed_base = transform.apply(nested_base);
|
||||||
|
let correction = transformed_zero - transformed_base;
|
||||||
|
stack.push(insert.block_name.clone());
|
||||||
|
for mut child in insert.explode_from_document(doc) {
|
||||||
|
child.as_entity_mut().translate(correction);
|
||||||
|
collect_custom_arrow_entity(
|
||||||
|
doc,
|
||||||
|
&child,
|
||||||
|
root_base,
|
||||||
|
lines,
|
||||||
|
fill,
|
||||||
|
stack,
|
||||||
|
depth + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wires = tessellate(
|
||||||
|
doc,
|
||||||
|
entity.common().handle,
|
||||||
|
entity,
|
||||||
|
false,
|
||||||
|
[1.0; 4],
|
||||||
|
0.0,
|
||||||
|
[0.0; 8],
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
[0.0, 0.0, 0.0, 1.0],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
for wire in wires {
|
||||||
|
append_custom_wire_points(&wire.points, &wire.points_low, root_base, lines);
|
||||||
|
append_custom_fill_points(&wire.fill_tris, &wire.fill_tris_low, root_base, fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_custom_wire_points(
|
||||||
|
points: &[[f32; 3]],
|
||||||
|
points_low: &[[f32; 3]],
|
||||||
|
base: acadrust::types::Vector3,
|
||||||
|
out: &mut Vec<[f32; 3]>,
|
||||||
|
) {
|
||||||
|
if points.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !out.is_empty() && !out.last().is_some_and(|p| p[0].is_nan()) {
|
||||||
|
out.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
for (index, point) in points.iter().enumerate() {
|
||||||
|
if point[0].is_nan() {
|
||||||
|
if !out.last().is_some_and(|p| p[0].is_nan()) {
|
||||||
|
out.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let low = points_low.get(index).copied().unwrap_or([0.0; 3]);
|
||||||
|
out.push([
|
||||||
|
(point[0] as f64 + low[0] as f64 - base.x) as f32,
|
||||||
|
(point[1] as f64 + low[1] as f64 - base.y) as f32,
|
||||||
|
(point[2] as f64 + low[2] as f64 - base.z) as f32,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_custom_fill_points(
|
||||||
|
points: &[[f32; 3]],
|
||||||
|
points_low: &[[f32; 3]],
|
||||||
|
base: acadrust::types::Vector3,
|
||||||
|
out: &mut Vec<[f32; 3]>,
|
||||||
|
) {
|
||||||
|
for (index, point) in points.iter().enumerate() {
|
||||||
|
let low = points_low.get(index).copied().unwrap_or([0.0; 3]);
|
||||||
|
out.push([
|
||||||
|
(point[0] as f64 + low[0] as f64 - base.x) as f32,
|
||||||
|
(point[1] as f64 + low[1] as f64 - base.y) as f32,
|
||||||
|
(point[2] as f64 + low[2] as f64 - base.z) as f32,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1775,43 +1968,43 @@ pub(crate) fn push_tri(out: &mut Vec<[f32; 3]>, a: Vec3, b: Vec3, c: Vec3) {
|
|||||||
pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowKind) {
|
pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowKind) {
|
||||||
let dir = normalized_or(dir, Vec3::X);
|
let dir = normalized_or(dir, Vec3::X);
|
||||||
let perp = Vec3::new(-dir.y, dir.x, 0.0);
|
let perp = Vec3::new(-dir.y, dir.x, 0.0);
|
||||||
match *arrow {
|
match arrow {
|
||||||
ArrowKind::None => {}
|
ArrowKind::None => {}
|
||||||
ArrowKind::Triangle {
|
ArrowKind::Triangle {
|
||||||
size,
|
size,
|
||||||
filled,
|
filled,
|
||||||
size_mul,
|
size_mul,
|
||||||
} => {
|
} => {
|
||||||
let size = size * size_mul;
|
let size = *size * *size_mul;
|
||||||
let base = tip + dir * size;
|
let base = tip + dir * size;
|
||||||
// ~1:6 length:half-width ratio (≈9.5° half-angle) matches
|
// ~1:6 length:half-width ratio (≈9.5° half-angle) matches
|
||||||
// AutoCAD's standard ClosedFilled block.
|
// the standard closed-filled block.
|
||||||
let half_w = size / 6.0;
|
let half_w = size / 6.0;
|
||||||
let left = base + perp * half_w;
|
let left = base + perp * half_w;
|
||||||
let right = base - perp * half_w;
|
let right = base - perp * half_w;
|
||||||
add_segment(&mut g.dim_lines, tip, left);
|
add_segment(&mut g.dim_lines, tip, left);
|
||||||
add_segment(&mut g.dim_lines, left, right);
|
add_segment(&mut g.dim_lines, left, right);
|
||||||
add_segment(&mut g.dim_lines, right, tip);
|
add_segment(&mut g.dim_lines, right, tip);
|
||||||
if filled {
|
if *filled {
|
||||||
push_tri(&mut g.arrow_fill, tip, left, right);
|
push_tri(&mut g.arrow_fill, tip, left, right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArrowKind::Tick { size } => {
|
ArrowKind::Tick { size } => {
|
||||||
// 45° oblique tick crossing the dim line at the tip; `size` is
|
// 45° oblique tick crossing the dim line at the tip; `size` is
|
||||||
// the half-length (matches AutoCAD's DIMTSZ semantics).
|
// the half-length used by DIMTSZ.
|
||||||
let off = (dir + perp).normalize_or_zero() * size;
|
let off = (dir + perp).normalize_or_zero() * *size;
|
||||||
add_segment(&mut g.dim_lines, tip - off, tip + off);
|
add_segment(&mut g.dim_lines, tip - off, tip + off);
|
||||||
}
|
}
|
||||||
ArrowKind::Open { size, half_angle } => {
|
ArrowKind::Open { size, half_angle } => {
|
||||||
let base = tip + dir * size;
|
let base = tip + dir * *size;
|
||||||
let half_w = size * half_angle.tan();
|
let half_w = *size * half_angle.tan();
|
||||||
let left = base + perp * half_w;
|
let left = base + perp * half_w;
|
||||||
let right = base - perp * half_w;
|
let right = base - perp * half_w;
|
||||||
add_segment(&mut g.dim_lines, tip, left);
|
add_segment(&mut g.dim_lines, tip, left);
|
||||||
add_segment(&mut g.dim_lines, tip, right);
|
add_segment(&mut g.dim_lines, tip, right);
|
||||||
}
|
}
|
||||||
ArrowKind::Dot { size, filled } => {
|
ArrowKind::Dot { size, filled } => {
|
||||||
let r = size * 0.5;
|
let r = *size * 0.5;
|
||||||
const N: usize = 16;
|
const N: usize = 16;
|
||||||
let mut ring: Vec<Vec3> = Vec::with_capacity(N + 1);
|
let mut ring: Vec<Vec3> = Vec::with_capacity(N + 1);
|
||||||
for i in 0..=N {
|
for i in 0..=N {
|
||||||
@@ -1819,7 +2012,7 @@ pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowK
|
|||||||
ring.push(tip + Vec3::new(a.cos() * r, a.sin() * r, 0.0));
|
ring.push(tip + Vec3::new(a.cos() * r, a.sin() * r, 0.0));
|
||||||
}
|
}
|
||||||
add_polyline(&mut g.dim_lines, &ring);
|
add_polyline(&mut g.dim_lines, &ring);
|
||||||
if filled {
|
if *filled {
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
push_tri(&mut g.arrow_fill, tip, ring[i], ring[i + 1]);
|
push_tri(&mut g.arrow_fill, tip, ring[i], ring[i + 1]);
|
||||||
}
|
}
|
||||||
@@ -1828,7 +2021,7 @@ pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowK
|
|||||||
ArrowKind::Origin { size } => {
|
ArrowKind::Origin { size } => {
|
||||||
// Small filled dot at the tip with a perpendicular tick crossing
|
// Small filled dot at the tip with a perpendicular tick crossing
|
||||||
// the dim line — matches "_ORIGIN" / "_ORIGIN2" blocks.
|
// the dim line — matches "_ORIGIN" / "_ORIGIN2" blocks.
|
||||||
let r = size * 0.25;
|
let r = *size * 0.25;
|
||||||
const N: usize = 12;
|
const N: usize = 12;
|
||||||
let mut ring: Vec<Vec3> = Vec::with_capacity(N + 1);
|
let mut ring: Vec<Vec3> = Vec::with_capacity(N + 1);
|
||||||
for i in 0..=N {
|
for i in 0..=N {
|
||||||
@@ -1839,11 +2032,11 @@ pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowK
|
|||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
push_tri(&mut g.arrow_fill, tip, ring[i], ring[i + 1]);
|
push_tri(&mut g.arrow_fill, tip, ring[i], ring[i + 1]);
|
||||||
}
|
}
|
||||||
let half = size * 0.5;
|
let half = *size * 0.5;
|
||||||
add_segment(&mut g.dim_lines, tip - perp * half, tip + perp * half);
|
add_segment(&mut g.dim_lines, tip - perp * half, tip + perp * half);
|
||||||
}
|
}
|
||||||
ArrowKind::Box_ { size, filled } => {
|
ArrowKind::Box_ { size, filled } => {
|
||||||
let half = size * 0.5;
|
let half = *size * 0.5;
|
||||||
let p1 = tip - dir * half - perp * half;
|
let p1 = tip - dir * half - perp * half;
|
||||||
let p2 = tip + dir * half - perp * half;
|
let p2 = tip + dir * half - perp * half;
|
||||||
let p3 = tip + dir * half + perp * half;
|
let p3 = tip + dir * half + perp * half;
|
||||||
@@ -1852,7 +2045,7 @@ pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowK
|
|||||||
add_segment(&mut g.dim_lines, p2, p3);
|
add_segment(&mut g.dim_lines, p2, p3);
|
||||||
add_segment(&mut g.dim_lines, p3, p4);
|
add_segment(&mut g.dim_lines, p3, p4);
|
||||||
add_segment(&mut g.dim_lines, p4, p1);
|
add_segment(&mut g.dim_lines, p4, p1);
|
||||||
if filled {
|
if *filled {
|
||||||
push_tri(&mut g.arrow_fill, p1, p2, p3);
|
push_tri(&mut g.arrow_fill, p1, p2, p3);
|
||||||
push_tri(&mut g.arrow_fill, p1, p3, p4);
|
push_tri(&mut g.arrow_fill, p1, p3, p4);
|
||||||
}
|
}
|
||||||
@@ -1860,17 +2053,47 @@ pub(crate) fn append_arrow(g: &mut DimGeom, tip: Vec3, dir: Vec3, arrow: &ArrowK
|
|||||||
ArrowKind::Datum { size, filled } => {
|
ArrowKind::Datum { size, filled } => {
|
||||||
// Right-pointing triangle with the base perpendicular to the dim
|
// Right-pointing triangle with the base perpendicular to the dim
|
||||||
// line at the tip and the apex along +dir.
|
// line at the tip and the apex along +dir.
|
||||||
let half = size * 0.5;
|
let half = *size * 0.5;
|
||||||
let base_a = tip + perp * half;
|
let base_a = tip + perp * half;
|
||||||
let base_b = tip - perp * half;
|
let base_b = tip - perp * half;
|
||||||
let apex = tip + dir * size;
|
let apex = tip + dir * *size;
|
||||||
add_segment(&mut g.dim_lines, base_a, apex);
|
add_segment(&mut g.dim_lines, base_a, apex);
|
||||||
add_segment(&mut g.dim_lines, apex, base_b);
|
add_segment(&mut g.dim_lines, apex, base_b);
|
||||||
add_segment(&mut g.dim_lines, base_b, base_a);
|
add_segment(&mut g.dim_lines, base_b, base_a);
|
||||||
if filled {
|
if *filled {
|
||||||
push_tri(&mut g.arrow_fill, base_a, apex, base_b);
|
push_tri(&mut g.arrow_fill, base_a, apex, base_b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ArrowKind::Custom { size, lines, fill } => {
|
||||||
|
let transform = |point: &[f32; 3]| {
|
||||||
|
tip - dir * (point[0] * *size) - perp * (point[1] * *size)
|
||||||
|
+ Vec3::Z * (point[2] * *size)
|
||||||
|
};
|
||||||
|
if !lines.is_empty()
|
||||||
|
&& !g.dim_lines.is_empty()
|
||||||
|
&& !g.dim_lines.last().is_some_and(|point| point[0].is_nan())
|
||||||
|
{
|
||||||
|
g.dim_lines.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
for point in lines {
|
||||||
|
if point[0].is_nan() {
|
||||||
|
if !g.dim_lines.last().is_some_and(|point| point[0].is_nan()) {
|
||||||
|
g.dim_lines.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let point = transform(point);
|
||||||
|
g.dim_lines.push([point.x, point.y, point.z]);
|
||||||
|
}
|
||||||
|
for triangle in fill.chunks_exact(3) {
|
||||||
|
push_tri(
|
||||||
|
&mut g.arrow_fill,
|
||||||
|
transform(&triangle[0]),
|
||||||
|
transform(&triangle[1]),
|
||||||
|
transform(&triangle[2]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user