feat(annotative): scale text/dims/tables/blocks at the annotation scale + editable property pickers
Render: annotative-ness is resolved centrally in scene::annotative::is_annotative (per-object context dictionary, legacy XDATA, or annotative style) so the bake and the properties panel agree. Text, dimensions, tables and blocks now display at the current annotation scale in model space. An annotative block scales as one uniform unit about its insertion point — its internal geometry and attributes are carried by that scale instead of being scaled individually, fixing the block-attribute size regression. Tables scale their column/row/margin geometry. Properties: the handle- and flag-backed rows are now editable pickers — MLEADER multileader style / text style / arrowhead / leader linetype, General Material, and Plot style (named plot-style mode only; the color-dependent mode stays read-only, which is correct). A picked name is resolved back to its handle in the update loop, where the document is in scope. Annotative Yes/No and the annotation-scale row are shown per object. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8ef65f59f8
commit
1b037a68aa
9 changed files with 537 additions and 226 deletions
|
|
@ -37,7 +37,7 @@ fn properties(ins: &Insert) -> Vec<PropSection> {
|
|||
},
|
||||
})
|
||||
.collect();
|
||||
vec![
|
||||
let mut sections = vec![
|
||||
PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
|
|
@ -54,24 +54,24 @@ fn properties(ins: &Insert) -> Vec<PropSection> {
|
|||
props: vec![
|
||||
ro("Name", "block", ins.block_name.clone()),
|
||||
edit("Rotation", "rotation", ins.rotation.to_degrees()),
|
||||
ro("Block Unit", "block_unit", String::new()),
|
||||
ro("Unit factor", "unit_factor", String::new()),
|
||||
ro(
|
||||
"Annotative",
|
||||
"annotative",
|
||||
if annotative { "Yes" } else { "No" },
|
||||
),
|
||||
ro("Block Unit", "block_unit", String::new()),
|
||||
ro("Unit factor", "unit_factor", String::new()),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
];
|
||||
// The Attributes group appears only when the block actually has attributes.
|
||||
if !attrs.is_empty() {
|
||||
sections.push(PropSection {
|
||||
title: "Attributes".into(),
|
||||
props: attrs,
|
||||
},
|
||||
PropSection {
|
||||
title: "Custom".into(),
|
||||
props: Vec::new(),
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
sections
|
||||
}
|
||||
|
||||
fn apply_geom_prop(ins: &mut Insert, field: &str, value: &str) {
|
||||
|
|
@ -206,12 +206,39 @@ pub(crate) fn append_insert_attribute_wires(
|
|||
if attmode == 0 {
|
||||
return;
|
||||
}
|
||||
// An annotative block scales as ONE uniform unit about its insertion point;
|
||||
// its attributes are carried by that scale (AutoCAD even forbids annotative
|
||||
// attributes inside annotative blocks, to avoid double-scaling). So pre-scale
|
||||
// the annotative attribute about the insertion point and tessellate it at a
|
||||
// neutral 1.0 — the text path must never scale it a second time.
|
||||
let annotative = ins
|
||||
.common
|
||||
.extended_data
|
||||
.get_record("AcAnnotativeData")
|
||||
.is_some();
|
||||
let block_scale = if annotative { anno_scale as f64 } else { 1.0 };
|
||||
let ip = ins.insert_point;
|
||||
let scale_about = |q: acadrust::types::Vector3| {
|
||||
acadrust::types::Vector3::new(
|
||||
ip.x + (q.x - ip.x) * block_scale,
|
||||
ip.y + (q.y - ip.y) * block_scale,
|
||||
ip.z + (q.z - ip.z) * block_scale,
|
||||
)
|
||||
};
|
||||
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 attr_entity = EntityType::AttributeEntity({
|
||||
let mut a = attr.clone();
|
||||
if (block_scale - 1.0).abs() > 1e-6 {
|
||||
a.height *= block_scale;
|
||||
a.insertion_point = scale_about(a.insertion_point);
|
||||
a.alignment_point = scale_about(a.alignment_point);
|
||||
}
|
||||
a
|
||||
});
|
||||
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) = render::render_style_for_block_sub(
|
||||
document,
|
||||
&attr_entity,
|
||||
|
|
@ -237,7 +264,9 @@ pub(crate) fn append_insert_attribute_wires(
|
|||
sub_plen * pslt_factor,
|
||||
sub_pat.map(|v| v * pslt_factor),
|
||||
sub_lw_px,
|
||||
anno_scale,
|
||||
// Neutral: the attribute is already scaled as part of the block unit
|
||||
// above, so the text path must not scale it a second time.
|
||||
1.0,
|
||||
None,
|
||||
bg_color,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -367,6 +367,10 @@ pub fn tessellate_table(
|
|||
selected: bool,
|
||||
entity_color: [f32; 4],
|
||||
line_weight_px: f32,
|
||||
// Annotation scale: multiplies the table's paper-size geometry so an
|
||||
// annotative table renders at the current annotation scale. 1.0 for a
|
||||
// non-annotative table (its geometry is already at model size).
|
||||
anno_scale: f32,
|
||||
) -> Vec<crate::scene::model::wire_model::WireModel> {
|
||||
use crate::scene::convert::tess_util::aci_to_rgba;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
|
@ -431,7 +435,7 @@ pub fn tessellate_table(
|
|||
let mut off = 0.0f32;
|
||||
let mut v = vec![0.0f32];
|
||||
for col in &tab.columns {
|
||||
off += col.width as f32;
|
||||
off += col.width as f32 * anno_scale;
|
||||
v.push(off);
|
||||
}
|
||||
v
|
||||
|
|
@ -440,7 +444,7 @@ pub fn tessellate_table(
|
|||
let mut off = 0.0f32;
|
||||
let mut v = vec![0.0f32];
|
||||
for row in &tab.rows {
|
||||
off += row.height as f32;
|
||||
off += row.height as f32 * anno_scale;
|
||||
v.push(off);
|
||||
}
|
||||
v
|
||||
|
|
@ -450,8 +454,8 @@ pub fn tessellate_table(
|
|||
let header_suppressed = table_style.map(|t| t.header_suppressed).unwrap_or(false);
|
||||
let h_margin = table_style
|
||||
.map(|t| t.horizontal_margin as f32)
|
||||
.unwrap_or(0.0);
|
||||
let v_margin = table_style.map(|t| t.vertical_margin as f32).unwrap_or(0.0);
|
||||
.unwrap_or(0.0) * anno_scale;
|
||||
let v_margin = table_style.map(|t| t.vertical_margin as f32).unwrap_or(0.0) * anno_scale;
|
||||
|
||||
let lookup_style = |hh: acadrust::Handle| -> Option<&acadrust::tables::TextStyle> {
|
||||
document.text_styles.iter().find(|s| s.handle == hh)
|
||||
|
|
@ -526,7 +530,7 @@ pub fn tessellate_table(
|
|||
let row_bot = row_offsets
|
||||
.get(ri + 1)
|
||||
.copied()
|
||||
.unwrap_or(row_top + row.height as f32);
|
||||
.unwrap_or(row_top + row.height as f32 * anno_scale);
|
||||
let row_mid = (row_top + row_bot) * 0.5;
|
||||
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style.map(|ts| {
|
||||
let kind = match (title_suppressed, header_suppressed, ri) {
|
||||
|
|
@ -544,7 +548,7 @@ pub fn tessellate_table(
|
|||
|
||||
for (ci, cell) in row.cells.iter().enumerate() {
|
||||
let col_left = col_offsets[ci];
|
||||
let col_width = tab.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0);
|
||||
let col_width = tab.columns.get(ci).map(|c| c.width as f32).unwrap_or(1.0) * anno_scale;
|
||||
let col_right = col_left + col_width;
|
||||
let tl = origin + h * col_left + v_flow * row_top;
|
||||
let tr = origin + h * col_right + v_flow * row_top;
|
||||
|
|
@ -648,7 +652,7 @@ pub fn tessellate_table(
|
|||
})
|
||||
.or_else(|| row_style.map(|s| s.text_height).filter(|h| *h > 1e-6))
|
||||
.map(|h| h as f32)
|
||||
.unwrap_or(0.18);
|
||||
.unwrap_or(0.18) * anno_scale;
|
||||
let m_h = if h_margin > 1e-6 {
|
||||
h_margin
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue