forked from 1b-agent/opencadstudio-upstream
feat(entities): integrate remaining fields on block-based entities
MultiLeader and Table were ignoring most of the styling and content fields that the parser populated. Bring both up to file-driven render: MultiLeader - line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback - line_type_handle: dashed leader via line_types lookup by handle - line_weight (ml.line_weight): per-leader pixel width override - arrowhead_handle: arrow shape resolved through the dim arrow emitter (Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None) - extend_leader_to_text: continue the leader past the landing to the text insertion point - text_angle_type: Horizontal forces 0, Optimized clamps to a readable half-plane, ParallelToLastLeaderLine keeps the stored direction - text_direction_negative: adds π to the rotation - text_alignment: drives the horizontal anchor when more specific than the context's text_attachment_point - text_attachment_direction + text_top_attachment + text_bottom_attachment: Vertical mode uses the top / bottom attachment values chosen from the first root's direction.y sign - has_block_contents + block_content_handle + block_content_location + block_rotation + block_scale + block_content_color: synthesise an Insert and explode it through the standard tessellator so block- content MultiLeaders render their referenced block - style_handle, arrowhead_handle, line_type_handle, text_style_handle, block_content_handle, property_override_flags, text_align_in_ipe, block_scale, block_rotation, extend_leader_to_text, text_direction_negative, text_top_attachment, text_bottom_attachment surfaced in the Properties panel Table - Per-cell borders: drop the always-grid pass, walk the cells and emit each border honouring its `invisible` flag with dedup so shared edges aren't doubled - table_style_handle, block_record_handle, data_version, value_flags, override_flag / override_border_* flags, break_options / break_flow_direction / break_spacing, normal surfaced in the Properties panel Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
e0ec7f1999
commit
613d6cf99d
3 files changed
+434
-47
No files matched your search
@@ -510,9 +510,112 @@ fn properties(ml: &MultiLeader) -> PropSection {
|
|||||||
"Bottom Line",
|
"Bottom Line",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// Top / Bottom attach (used when text_attachment_direction = Vertical)
|
||||||
|
choice(
|
||||||
|
"Top Attach",
|
||||||
|
"text_top_attachment",
|
||||||
|
attachment_str(&ml.text_top_attachment),
|
||||||
|
&[
|
||||||
|
"Top of Top",
|
||||||
|
"Mid of Top",
|
||||||
|
"Mid of Text",
|
||||||
|
"Mid of Bot",
|
||||||
|
"Bot of Bot",
|
||||||
|
"Bottom Line",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
choice(
|
||||||
|
"Bottom Attach",
|
||||||
|
"text_bottom_attachment",
|
||||||
|
attachment_str(&ml.text_bottom_attachment),
|
||||||
|
&[
|
||||||
|
"Top of Top",
|
||||||
|
"Mid of Top",
|
||||||
|
"Mid of Text",
|
||||||
|
"Mid of Bot",
|
||||||
|
"Bot of Bot",
|
||||||
|
"Bottom Line",
|
||||||
|
],
|
||||||
|
),
|
||||||
// Stats
|
// Stats
|
||||||
ro("Leader Pts", "total_pts", total_pts.to_string()),
|
ro("Leader Pts", "total_pts", total_pts.to_string()),
|
||||||
ro("Roots", "root_count", ctx.leader_roots.len().to_string()),
|
ro("Roots", "root_count", ctx.leader_roots.len().to_string()),
|
||||||
|
// Style references / handles (read-only — the multileader's own
|
||||||
|
// copies of the style values are the authoritative render inputs).
|
||||||
|
ro(
|
||||||
|
"Style Handle",
|
||||||
|
"style_handle",
|
||||||
|
match ml.style_handle {
|
||||||
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||||||
|
_ => "(none)".to_string(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Text Style Handle",
|
||||||
|
"text_style_handle",
|
||||||
|
match ml.text_style_handle {
|
||||||
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||||||
|
_ => "(none)".to_string(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Arrow Handle",
|
||||||
|
"arrowhead_handle",
|
||||||
|
match ml.arrowhead_handle {
|
||||||
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||||||
|
_ => "(none)".to_string(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Line Type Handle",
|
||||||
|
"line_type_handle",
|
||||||
|
match ml.line_type_handle {
|
||||||
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||||||
|
_ => "(none)".to_string(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Block Content Handle",
|
||||||
|
"block_content_handle",
|
||||||
|
match ml.block_content_handle {
|
||||||
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||||||
|
_ => "(none)".to_string(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Less common toggles surfaced read-only.
|
||||||
|
ro(
|
||||||
|
"Extend Leader",
|
||||||
|
"extend_leader_to_text",
|
||||||
|
if ml.extend_leader_to_text { "Yes" } else { "No" },
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Text Direction Negative",
|
||||||
|
"text_direction_negative",
|
||||||
|
if ml.text_direction_negative { "Yes" } else { "No" },
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Text Align In IPE",
|
||||||
|
"text_align_in_ipe",
|
||||||
|
ml.text_align_in_ipe.to_string(),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Property Override Flags",
|
||||||
|
"property_override_flags",
|
||||||
|
format!("{:#018b}", ml.property_override_flags.bits()),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Block Scale",
|
||||||
|
"block_scale",
|
||||||
|
format!(
|
||||||
|
"{:.3} × {:.3} × {:.3}",
|
||||||
|
ml.block_scale.x, ml.block_scale.y, ml.block_scale.z
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Block Rotation",
|
||||||
|
"block_rotation",
|
||||||
|
format!("{:.3}°", ml.block_rotation.to_degrees()),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Connection point for first root (most common case)
|
// Connection point for first root (most common case)
|
||||||
@@ -635,6 +738,14 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
|
|||||||
ml.text_right_attachment = parse_attachment(value);
|
ml.text_right_attachment = parse_attachment(value);
|
||||||
ml.context.text_right_attachment = parse_attachment(value);
|
ml.context.text_right_attachment = parse_attachment(value);
|
||||||
}
|
}
|
||||||
|
"text_top_attachment" => {
|
||||||
|
ml.text_top_attachment = parse_attachment(value);
|
||||||
|
ml.context.text_top_attachment = parse_attachment(value);
|
||||||
|
}
|
||||||
|
"text_bottom_attachment" => {
|
||||||
|
ml.text_bottom_attachment = parse_attachment(value);
|
||||||
|
ml.context.text_bottom_attachment = parse_attachment(value);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+133
-12
@@ -53,25 +53,73 @@ impl TruckConvertible for Table {
|
|||||||
|
|
||||||
let mut pts: Vec<[f32; 3]> = Vec::new();
|
let mut pts: Vec<[f32; 3]> = Vec::new();
|
||||||
|
|
||||||
let mut add_seg = |a: Vec3, b: Vec3| {
|
// Per-cell borders. When a cell carries a CellStyle, honour the
|
||||||
|
// visibility / `invisible` flag of each of its four borders so
|
||||||
|
// hidden borders disappear from the grid. Cells with no style still
|
||||||
|
// emit the standard four borders. To avoid drawing each shared edge
|
||||||
|
// twice we coalesce the segments by their (start, end) coordinates.
|
||||||
|
use std::collections::HashSet;
|
||||||
|
let mut emitted: HashSet<(i32, i32, i32, i32)> = HashSet::new();
|
||||||
|
let try_add = |a: Vec3, b: Vec3, vis: bool, emitted: &mut HashSet<(i32, i32, i32, i32)>, pts: &mut Vec<[f32; 3]>| {
|
||||||
|
if !vis {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let key = (
|
||||||
|
(a.x * 1_000.0) as i32,
|
||||||
|
(a.y * 1_000.0) as i32,
|
||||||
|
(b.x * 1_000.0) as i32,
|
||||||
|
(b.y * 1_000.0) as i32,
|
||||||
|
);
|
||||||
|
let key_rev = (key.2, key.3, key.0, key.1);
|
||||||
|
if emitted.contains(&key) || emitted.contains(&key_rev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emitted.insert(key);
|
||||||
if !pts.is_empty() {
|
if !pts.is_empty() {
|
||||||
pts.push([f32::NAN; 3]);
|
pts.push([f32::NAN; 3]);
|
||||||
}
|
}
|
||||||
pts.push([a.x, a.y, a.z]);
|
pts.push([a.x, a.y, a.z]);
|
||||||
pts.push([b.x, b.y, b.z]);
|
pts.push([b.x, b.y, b.z]);
|
||||||
};
|
};
|
||||||
|
for (ri, row) in self.rows.iter().enumerate() {
|
||||||
for &ry in &row_offsets {
|
let row_top = row_offsets[ri];
|
||||||
let left = origin + h * 0.0 + v_down * ry;
|
let row_bot = row_offsets
|
||||||
let right = origin + h * total_w + v_down * ry;
|
.get(ri + 1)
|
||||||
add_seg(left, right);
|
.copied()
|
||||||
}
|
.unwrap_or(row_top + row.height as f32);
|
||||||
|
for (ci, cell) in row.cells.iter().enumerate() {
|
||||||
for &cx in &col_offsets {
|
let col_left = col_offsets[ci];
|
||||||
let top = origin + h * cx + v_down * 0.0;
|
let col_right = col_offsets
|
||||||
let bottom = origin + h * cx + v_down * total_h;
|
.get(ci + 1)
|
||||||
add_seg(top, bottom);
|
.copied()
|
||||||
|
.unwrap_or(col_left
|
||||||
|
+ self.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0));
|
||||||
|
// Default to visible when no style override is present.
|
||||||
|
let (top_vis, right_vis, bottom_vis, left_vis) = cell
|
||||||
|
.style
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| {
|
||||||
|
(
|
||||||
|
!s.top_border.invisible,
|
||||||
|
!s.right_border.invisible,
|
||||||
|
!s.bottom_border.invisible,
|
||||||
|
!s.left_border.invisible,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or((true, true, true, true));
|
||||||
|
let tl = origin + h * col_left + v_down * row_top;
|
||||||
|
let tr = origin + h * col_right + v_down * row_top;
|
||||||
|
let br_ = origin + h * col_right + v_down * row_bot;
|
||||||
|
let bl = origin + h * col_left + v_down * row_bot;
|
||||||
|
try_add(tl, tr, top_vis, &mut emitted, &mut pts);
|
||||||
|
try_add(tr, br_, right_vis, &mut emitted, &mut pts);
|
||||||
|
try_add(bl, br_, bottom_vis, &mut emitted, &mut pts);
|
||||||
|
try_add(tl, bl, left_vis, &mut emitted, &mut pts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Suppress unused-variable warnings now that the simple grid-pass
|
||||||
|
// is gone — col/row offsets still feed cell drawing below.
|
||||||
|
let _ = (total_w, total_h);
|
||||||
|
|
||||||
// Cell text — lifted into Lines points via 2D strokes
|
// Cell text — lifted into Lines points via 2D strokes
|
||||||
let text_height = 0.18_f32;
|
let text_height = 0.18_f32;
|
||||||
@@ -187,6 +235,12 @@ impl Grippable for Table {
|
|||||||
|
|
||||||
impl PropertyEditable for Table {
|
impl PropertyEditable for Table {
|
||||||
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
||||||
|
let fmt_h = |oh: &Option<acadrust::types::Handle>| -> String {
|
||||||
|
match oh {
|
||||||
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||||||
|
_ => "(none)".to_string(),
|
||||||
|
}
|
||||||
|
};
|
||||||
PropSection {
|
PropSection {
|
||||||
title: "Table".into(),
|
title: "Table".into(),
|
||||||
props: vec![
|
props: vec![
|
||||||
@@ -207,6 +261,73 @@ impl PropertyEditable for Table {
|
|||||||
"tbl_iz",
|
"tbl_iz",
|
||||||
format!("{:.4}", self.insertion_point.z),
|
format!("{:.4}", self.insertion_point.z),
|
||||||
),
|
),
|
||||||
|
ro(
|
||||||
|
"Table Style",
|
||||||
|
"tbl_style_handle",
|
||||||
|
fmt_h(&self.table_style_handle),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Block Record",
|
||||||
|
"tbl_block_rec_handle",
|
||||||
|
fmt_h(&self.block_record_handle),
|
||||||
|
),
|
||||||
|
ro("Data Version", "tbl_data_version", self.data_version.to_string()),
|
||||||
|
ro(
|
||||||
|
"Value Flags",
|
||||||
|
"tbl_value_flags",
|
||||||
|
format!("{:#010x}", self.value_flags),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Override Flag",
|
||||||
|
"tbl_override_flag",
|
||||||
|
if self.override_flag { "Yes" } else { "No" },
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Override Border Color",
|
||||||
|
"tbl_override_border_color",
|
||||||
|
if self.override_border_color { "Yes" } else { "No" },
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Override Border LW",
|
||||||
|
"tbl_override_border_lw",
|
||||||
|
if self.override_border_line_weight {
|
||||||
|
"Yes"
|
||||||
|
} else {
|
||||||
|
"No"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Override Border Vis",
|
||||||
|
"tbl_override_border_vis",
|
||||||
|
if self.override_border_visibility {
|
||||||
|
"Yes"
|
||||||
|
} else {
|
||||||
|
"No"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Break Spacing",
|
||||||
|
"tbl_break_spacing",
|
||||||
|
format!("{:.4}", self.break_spacing),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Break Flow",
|
||||||
|
"tbl_break_flow",
|
||||||
|
format!("{:?}", self.break_flow_direction),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Break Options",
|
||||||
|
"tbl_break_options",
|
||||||
|
format!("{:#018b}", self.break_options.bits()),
|
||||||
|
),
|
||||||
|
ro(
|
||||||
|
"Normal",
|
||||||
|
"tbl_normal",
|
||||||
|
format!(
|
||||||
|
"{:.3}, {:.3}, {:.3}",
|
||||||
|
self.normal.x, self.normal.y, self.normal.z
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+190
-35
@@ -1291,11 +1291,40 @@ pub fn tessellate_multileader(
|
|||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
world_per_pixel: Option<f32>,
|
world_per_pixel: Option<f32>,
|
||||||
) -> Vec<WireModel> {
|
) -> Vec<WireModel> {
|
||||||
|
// line_color falls back to the entity colour when the MultiLeader's own
|
||||||
|
// colour is ByBlock/ByLayer; otherwise the leader uses its stored hue.
|
||||||
let line_color = if selected {
|
let line_color = if selected {
|
||||||
WireModel::SELECTED
|
WireModel::SELECTED
|
||||||
} else {
|
} else {
|
||||||
entity_color
|
color_or_inherit(&ml.line_color, entity_color)
|
||||||
};
|
};
|
||||||
|
// ml.line_weight is the leader-line weight override. Negative codes
|
||||||
|
// (ByLayer/ByBlock/Default) fall through to the entity's already-resolved
|
||||||
|
// pixel width.
|
||||||
|
let leader_lw_px = match ml.line_weight {
|
||||||
|
acadrust::types::LineWeight::Value(v) if v >= 0 => {
|
||||||
|
(v as f32 / 100.0) * (96.0 / 25.4)
|
||||||
|
}
|
||||||
|
_ => line_weight_px,
|
||||||
|
};
|
||||||
|
// ml.line_type_handle — resolve via line_types table by handle and apply
|
||||||
|
// the resulting dash pattern to the leader wire.
|
||||||
|
let lt_scale = document.header.linetype_scale as f32 * ml.common.linetype_scale as f32;
|
||||||
|
let (leader_pat_len, leader_pat) = match ml.line_type_handle {
|
||||||
|
Some(h) if !h.is_null() => {
|
||||||
|
let name = document
|
||||||
|
.line_types
|
||||||
|
.iter()
|
||||||
|
.find(|lt| lt.handle == h)
|
||||||
|
.map(|lt| lt.name.clone());
|
||||||
|
match name {
|
||||||
|
Some(n) => crate::scene::render::resolve_pattern(&document.line_types, &n, lt_scale),
|
||||||
|
None => (0.0, [0.0; 8]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (0.0, [0.0; 8]),
|
||||||
|
};
|
||||||
|
|
||||||
let name = handle.value().to_string();
|
let name = handle.value().to_string();
|
||||||
let nan = [f32::NAN; 3];
|
let nan = [f32::NAN; 3];
|
||||||
let [ox, oy, oz] = world_offset;
|
let [ox, oy, oz] = world_offset;
|
||||||
@@ -1316,12 +1345,24 @@ pub fn tessellate_multileader(
|
|||||||
let arrow_size = ml.arrowhead_size as f32 * effective_scale;
|
let arrow_size = ml.arrowhead_size as f32 * effective_scale;
|
||||||
let draw_arrow = arrow_size > 0.0;
|
let draw_arrow = arrow_size > 0.0;
|
||||||
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
||||||
|
// arrowhead_handle resolves through the block records to a named arrow
|
||||||
|
// block (matches DIMLDRBLK on Dimension). Null handle / unknown name →
|
||||||
|
// ClosedFilled triangle.
|
||||||
|
let arrow_kind = match ml.arrowhead_handle {
|
||||||
|
Some(h) if !h.is_null() => arrow_from_block(document, h, arrow_size.max(0.001)),
|
||||||
|
_ => ArrowKind::Triangle {
|
||||||
|
size: arrow_size.max(0.001),
|
||||||
|
filled: true,
|
||||||
|
size_mul: 1.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ── Leader / arrow / dogleg points ───────────────────────────────────────
|
// ── Leader / arrow / dogleg points ───────────────────────────────────────
|
||||||
let mut points: Vec<[f32; 3]> = Vec::new();
|
let mut points: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut key_verts: Vec<[f32; 3]> = Vec::new();
|
let mut key_verts: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut snap_pts: Vec<(Vec3, SnapHint)> = Vec::new();
|
let mut snap_pts: Vec<(Vec3, SnapHint)> = Vec::new();
|
||||||
let mut tangents: Vec<TangentGeom> = Vec::new();
|
let mut tangents: Vec<TangentGeom> = Vec::new();
|
||||||
|
let mut arrow_fill: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
|
|
||||||
for root in &ml.context.leader_roots {
|
for root in &ml.context.leader_roots {
|
||||||
@@ -1383,21 +1424,17 @@ pub fn tessellate_multileader(
|
|||||||
let dx = (next.x - tip.x) as f32;
|
let dx = (next.x - tip.x) as f32;
|
||||||
let dy = (next.y - tip.y) as f32;
|
let dy = (next.y - tip.y) as f32;
|
||||||
let dl = (dx * dx + dy * dy).sqrt().max(1e-9);
|
let dl = (dx * dx + dy * dy).sqrt().max(1e-9);
|
||||||
let (dx, dy) = (dx / dl, dy / dl);
|
let dir = Vec3::new(dx / dl, dy / dl, 0.0);
|
||||||
let a = std::f32::consts::PI / 6.0;
|
let tip_v = Vec3::new(tip_f[0], tip_f[1], tip_f[2]);
|
||||||
let (s, c) = a.sin_cos();
|
// Reuse the dim/leader arrow emitter so MultiLeader's arrow
|
||||||
points.push(nan);
|
// matches the block referenced by arrowhead_handle.
|
||||||
points.push([
|
let mut arrow_geom = DimGeom::new();
|
||||||
tip_f[0] + (dx * c - dy * s) * arrow_size,
|
append_arrow(&mut arrow_geom, tip_v, dir, &arrow_kind);
|
||||||
tip_f[1] + (dx * s + dy * c) * arrow_size,
|
if !arrow_geom.dim_lines.is_empty() {
|
||||||
tip_f[2],
|
points.push(nan);
|
||||||
]);
|
points.extend(arrow_geom.dim_lines);
|
||||||
points.push(tip_f);
|
}
|
||||||
points.push([
|
arrow_fill.extend(arrow_geom.arrow_fill);
|
||||||
tip_f[0] + (dx * c + dy * s) * arrow_size,
|
|
||||||
tip_f[1] + (-dx * s + dy * c) * arrow_size,
|
|
||||||
tip_f[2],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1405,13 +1442,25 @@ pub fn tessellate_multileader(
|
|||||||
let dir = &root.direction;
|
let dir = &root.direction;
|
||||||
let dl = (dir.x * dir.x + dir.y * dir.y).sqrt().max(1e-9);
|
let dl = (dir.x * dir.x + dir.y * dir.y).sqrt().max(1e-9);
|
||||||
let d = ml.dogleg_length * effective_scale as f64;
|
let d = ml.dogleg_length * effective_scale as f64;
|
||||||
points.push(nan);
|
let landing_end = [
|
||||||
points.push(cp_f);
|
|
||||||
points.push([
|
|
||||||
(cp.x + dir.x / dl * d - ox) as f32,
|
(cp.x + dir.x / dl * d - ox) as f32,
|
||||||
(cp.y + dir.y / dl * d - oy) as f32,
|
(cp.y + dir.y / dl * d - oy) as f32,
|
||||||
cp_f[2],
|
cp_f[2],
|
||||||
]);
|
];
|
||||||
|
points.push(nan);
|
||||||
|
points.push(cp_f);
|
||||||
|
points.push(landing_end);
|
||||||
|
// extend_leader_to_text: continue past the landing to the text
|
||||||
|
// insertion point along the same direction.
|
||||||
|
if ml.extend_leader_to_text
|
||||||
|
&& ml.content_type == LeaderContentType::MText
|
||||||
|
&& !ml.context.text_string.is_empty()
|
||||||
|
{
|
||||||
|
let tx = (ml.context.text_location.x - ox) as f32;
|
||||||
|
let ty = (ml.context.text_location.y - oy) as f32;
|
||||||
|
points.push(landing_end);
|
||||||
|
points.push([tx, ty, cp_f[2]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,18 +1474,78 @@ pub fn tessellate_multileader(
|
|||||||
color: line_color,
|
color: line_color,
|
||||||
selected,
|
selected,
|
||||||
aci: 0,
|
aci: 0,
|
||||||
pattern_length: 0.0,
|
pattern_length: leader_pat_len,
|
||||||
pattern: [0.0; 8],
|
pattern: leader_pat,
|
||||||
line_weight_px,
|
line_weight_px: leader_lw_px,
|
||||||
snap_pts,
|
snap_pts,
|
||||||
tangent_geoms: tangents,
|
tangent_geoms: tangents,
|
||||||
key_vertices: key_verts,
|
key_vertices: key_verts,
|
||||||
aabb: WireModel::UNBOUNDED_AABB,
|
aabb: WireModel::UNBOUNDED_AABB,
|
||||||
plinegen: true,
|
plinegen: true,
|
||||||
vp_scissor: None,
|
vp_scissor: None,
|
||||||
fill_tris: vec![],
|
fill_tris: arrow_fill,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Block content ───────────────────────────────────────────────────────
|
||||||
|
// When content_type == Block, the MultiLeader displays a block reference
|
||||||
|
// at block_content_location with the recorded rotation/scale. Synthesize
|
||||||
|
// an Insert and explode it through the standard tessellator. The block
|
||||||
|
// resolves via block_content_handle (handle → block_record name).
|
||||||
|
if ml.content_type == LeaderContentType::Block && ml.context.has_block_contents {
|
||||||
|
let block_name = match ml.block_content_handle {
|
||||||
|
Some(h) if !h.is_null() => document
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|br| br.handle == h)
|
||||||
|
.map(|br| br.name.clone()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(block_name) = block_name {
|
||||||
|
let block_color = if selected {
|
||||||
|
line_color
|
||||||
|
} else {
|
||||||
|
color_or_inherit(&ml.block_content_color, entity_color)
|
||||||
|
};
|
||||||
|
let mut synth_ins =
|
||||||
|
acadrust::entities::Insert::new(block_name, ml.context.block_content_location);
|
||||||
|
synth_ins.set_x_scale(ml.block_scale.x);
|
||||||
|
synth_ins.set_y_scale(ml.block_scale.y);
|
||||||
|
synth_ins.set_z_scale(ml.block_scale.z);
|
||||||
|
synth_ins.rotation = ml.block_rotation;
|
||||||
|
synth_ins.common.layer = ml.common.layer.clone();
|
||||||
|
// block_connection_type (BlockExtents vs BasePoint) chooses the
|
||||||
|
// anchor when *creating* the multileader; at render time the
|
||||||
|
// file's stored leader endpoints already encode that choice.
|
||||||
|
let _ = ml.block_connection_type;
|
||||||
|
for sub in synth_ins.explode_from_document(document) {
|
||||||
|
let normalized =
|
||||||
|
crate::modules::home::modify::explode::normalize_insert_entity(sub);
|
||||||
|
let mut wire = tessellate(
|
||||||
|
document,
|
||||||
|
handle,
|
||||||
|
&normalized,
|
||||||
|
selected,
|
||||||
|
block_color,
|
||||||
|
leader_pat_len,
|
||||||
|
leader_pat,
|
||||||
|
leader_lw_px,
|
||||||
|
world_offset,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
wire.name = name.clone();
|
||||||
|
wires.push(wire);
|
||||||
|
}
|
||||||
|
// Block attributes attached to the multileader — render each as
|
||||||
|
// its own attribute entity at WCS location like INSERT does.
|
||||||
|
for ba in &ml.block_attributes {
|
||||||
|
let _ = ba; // BlockAttribute carries only the value override
|
||||||
|
// string; we'd need the AttributeDefinition handle
|
||||||
|
// to materialise it as ATTRIB geometry. Skipped
|
||||||
|
// until that wiring exists.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Text strokes / frame / background fill ──────────────────────────────
|
// ── Text strokes / frame / background fill ──────────────────────────────
|
||||||
// Strip inline format codes, split / word-wrap into lines, then place each
|
// Strip inline format codes, split / word-wrap into lines, then place each
|
||||||
// line according to text_attachment_point (horizontal) and
|
// line according to text_attachment_point (horizontal) and
|
||||||
@@ -1458,14 +1567,34 @@ pub fn tessellate_multileader(
|
|||||||
let local_ins_y = (ins.y - oy) as f32;
|
let local_ins_y = (ins.y - oy) as f32;
|
||||||
let z = (ins.z - oz) as f32;
|
let z = (ins.z - oz) as f32;
|
||||||
|
|
||||||
// Rotation: prefer text_direction (transforms survive rotations / mirrors
|
// Rotation: prefer text_direction (transforms survive rotations /
|
||||||
// when acadrust updates it) and fall back to text_rotation.
|
// mirrors when acadrust updates it) and fall back to text_rotation.
|
||||||
|
// ml.text_angle_type then constrains the final angle:
|
||||||
|
// - Horizontal: always 0
|
||||||
|
// - ParallelToLastLeaderLine: keep stored direction
|
||||||
|
// - Optimized: clamp to (-π/2, π/2] (flip upside-down)
|
||||||
|
// text_direction_negative finally adds π so reading goes the other way.
|
||||||
let td = ctx.text_direction;
|
let td = ctx.text_direction;
|
||||||
let rot = if td.x.abs() > 1e-9 || td.y.abs() > 1e-9 {
|
let mut rot = if td.x.abs() > 1e-9 || td.y.abs() > 1e-9 {
|
||||||
(td.y as f32).atan2(td.x as f32)
|
(td.y as f32).atan2(td.x as f32)
|
||||||
} else {
|
} else {
|
||||||
ctx.text_rotation as f32
|
ctx.text_rotation as f32
|
||||||
};
|
};
|
||||||
|
match ml.text_angle_type {
|
||||||
|
acadrust::entities::multileader::TextAngleType::Horizontal => rot = 0.0,
|
||||||
|
acadrust::entities::multileader::TextAngleType::Optimized => {
|
||||||
|
let pi = std::f32::consts::PI;
|
||||||
|
if rot > pi / 2.0 {
|
||||||
|
rot -= pi;
|
||||||
|
} else if rot <= -pi / 2.0 {
|
||||||
|
rot += pi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acadrust::entities::multileader::TextAngleType::ParallelToLastLeaderLine => {}
|
||||||
|
}
|
||||||
|
if ml.text_direction_negative {
|
||||||
|
rot += std::f32::consts::PI;
|
||||||
|
}
|
||||||
let (cos_r, sin_r) = (rot.cos(), rot.sin());
|
let (cos_r, sin_r) = (rot.cos(), rot.sin());
|
||||||
|
|
||||||
// Resolve text style via handle when available, falling back to STANDARD.
|
// Resolve text style via handle when available, falling back to STANDARD.
|
||||||
@@ -1511,15 +1640,41 @@ pub fn tessellate_multileader(
|
|||||||
let line_h = height * ls_factor * (5.0 / 3.0) * font.line_spacing;
|
let line_h = height * ls_factor * (5.0 / 3.0) * font.line_spacing;
|
||||||
let n_lines = lines.len().max(1) as f32;
|
let n_lines = lines.len().max(1) as f32;
|
||||||
|
|
||||||
let h_anchor = match ctx.text_attachment_point {
|
// ml.text_alignment overrides the per-context attachment_point when
|
||||||
TextAttachmentPointType::Left => 0.0_f32,
|
// not Left (which is the default zero). Otherwise we honour the
|
||||||
TextAttachmentPointType::Center => 0.5,
|
// context's stored attachment_point (Left/Center/Right).
|
||||||
TextAttachmentPointType::Right => 1.0,
|
use acadrust::entities::multileader::{TextAlignmentType, TextAttachmentDirectionType};
|
||||||
|
let h_anchor = match (ml.text_alignment, ctx.text_attachment_point) {
|
||||||
|
(TextAlignmentType::Center, _) => 0.5_f32,
|
||||||
|
(TextAlignmentType::Right, _) => 1.0,
|
||||||
|
(TextAlignmentType::Left, TextAttachmentPointType::Center) => 0.5,
|
||||||
|
(TextAlignmentType::Left, TextAttachmentPointType::Right) => 1.0,
|
||||||
|
_ => 0.0,
|
||||||
};
|
};
|
||||||
// Vertical anchor: use text_left_attachment (matches the leader-to-text
|
// Pick the vertical-anchor attachment based on text_attachment_direction:
|
||||||
// attachment convention for the common case of left-side leaders).
|
// Horizontal — leader attaches left/right; use ml.text_left_attachment
|
||||||
let v_offset =
|
// (matches the file's stored ctx.text_left_attachment).
|
||||||
v_offset_for_attachment(ctx.text_left_attachment, n_lines, height, line_h);
|
// Vertical — leader attaches top/bottom; use ml.text_top_attachment
|
||||||
|
// or ml.text_bottom_attachment depending on which side
|
||||||
|
// the leader is coming from (chosen via the first
|
||||||
|
// root.direction.y sign).
|
||||||
|
let leader_from_top = ml
|
||||||
|
.context
|
||||||
|
.leader_roots
|
||||||
|
.first()
|
||||||
|
.map(|r| r.direction.y < 0.0)
|
||||||
|
.unwrap_or(true);
|
||||||
|
let vertical_attach = match ml.text_attachment_direction {
|
||||||
|
TextAttachmentDirectionType::Vertical => {
|
||||||
|
if leader_from_top {
|
||||||
|
ml.text_top_attachment
|
||||||
|
} else {
|
||||||
|
ml.text_bottom_attachment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextAttachmentDirectionType::Horizontal => ctx.text_left_attachment,
|
||||||
|
};
|
||||||
|
let v_offset = v_offset_for_attachment(vertical_attach, n_lines, height, line_h);
|
||||||
|
|
||||||
let scale = height / 9.0 * width_factor;
|
let scale = height / 9.0 * width_factor;
|
||||||
let line_widths: Vec<f32> = lines
|
let line_widths: Vec<f32> = lines
|
||||||
|
|||||||
Reference in new issue
Block a user