forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
8ef65f59f8
commit
1b037a68aa
9 files changed
+539
-228
No files matched your search
+189
-182
@@ -95,95 +95,177 @@ impl OpenCADStudio {
|
|||||||
let mut sections =
|
let mut sections =
|
||||||
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
||||||
|
|
||||||
// Resolve a custom material handle (material_flags == 3) to
|
// Turn the Material row into an editable picker: the source
|
||||||
// its name in the 3D Visualization group.
|
// options (ByLayer / ByBlock) plus every named material the
|
||||||
|
// drawing defines. A custom flag (3) shows the stored handle's
|
||||||
|
// material name as the selection.
|
||||||
{
|
{
|
||||||
|
let doc = &self.tabs[i].scene.document;
|
||||||
let common = entity.common();
|
let common = entity.common();
|
||||||
if common.material_flags == 3 {
|
let mat_names: Vec<String> = doc
|
||||||
if let Some(mh) = common.material_handle {
|
.objects
|
||||||
if let Some(name) = self.tabs[i]
|
.iter()
|
||||||
.scene
|
.filter_map(|(_, o)| match o {
|
||||||
.document
|
acadrust::objects::ObjectType::Material(m) => Some(m.name.clone()),
|
||||||
.objects
|
_ => None,
|
||||||
.iter()
|
})
|
||||||
.find_map(|(h, o)| match o {
|
.collect();
|
||||||
|
let selected = match common.material_flags {
|
||||||
|
0 => "ByLayer".to_string(),
|
||||||
|
1 => "ByBlock".to_string(),
|
||||||
|
_ => common
|
||||||
|
.material_handle
|
||||||
|
.and_then(|mh| {
|
||||||
|
doc.objects.iter().find_map(|(h, o)| match o {
|
||||||
acadrust::objects::ObjectType::Material(m) if *h == mh => {
|
acadrust::objects::ObjectType::Material(m) if *h == mh => {
|
||||||
Some(m.name.clone())
|
Some(m.name.clone())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "ByLayer".to_string()),
|
||||||
|
};
|
||||||
|
let mut options = vec!["ByLayer".to_string(), "ByBlock".to_string()];
|
||||||
|
options.extend(mat_names);
|
||||||
|
for section in sections.iter_mut() {
|
||||||
|
if let Some(row) =
|
||||||
|
section.props.iter_mut().find(|p| p.field == "material")
|
||||||
|
{
|
||||||
|
row.value = crate::scene::model::object::PropValue::Choice {
|
||||||
|
selected: selected.clone(),
|
||||||
|
options: options.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In named plot-style mode (PSTYLEMODE=1) the Plot style row
|
||||||
|
// picks a named style from the drawing's plot style table. In
|
||||||
|
// the color-dependent mode it stays read-only (the object's
|
||||||
|
// color drives the plot style), so it is left untouched.
|
||||||
|
if self.tabs[i].scene.document.header.plotstyle_mode {
|
||||||
|
let doc = &self.tabs[i].scene.document;
|
||||||
|
let dict_h = doc.header.acad_plotstylename_dict_handle;
|
||||||
|
if let Some(dict) = crate::scene::annotative::as_dict(doc, dict_h) {
|
||||||
|
let common = entity.common();
|
||||||
|
let mut options = vec!["ByLayer".to_string(), "ByBlock".to_string()];
|
||||||
|
options.extend(dict.entries.iter().map(|(n, _)| n.clone()));
|
||||||
|
let selected = match common.plotstyle_flags {
|
||||||
|
0 => "ByLayer".to_string(),
|
||||||
|
1 => "ByBlock".to_string(),
|
||||||
|
_ => common
|
||||||
|
.plotstyle_handle
|
||||||
|
.and_then(|ph| {
|
||||||
|
dict.entries
|
||||||
|
.iter()
|
||||||
|
.find(|(_, h)| *h == ph)
|
||||||
|
.map(|(n, _)| n.clone())
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "ByLayer".to_string()),
|
||||||
|
};
|
||||||
|
for section in sections.iter_mut() {
|
||||||
|
if let Some(row) =
|
||||||
|
section.props.iter_mut().find(|p| p.field == "plot_style")
|
||||||
{
|
{
|
||||||
for section in sections.iter_mut() {
|
row.value = crate::scene::model::object::PropValue::Choice {
|
||||||
if let Some(row) =
|
selected: selected.clone(),
|
||||||
section.props.iter_mut().find(|p| p.field == "material")
|
options: options.clone(),
|
||||||
{
|
};
|
||||||
row.value =
|
|
||||||
crate::scene::model::object::PropValue::ReadOnly(
|
|
||||||
name.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve MLEADER handle-backed rows (multileader style, text
|
// Turn the MLEADER handle-backed rows (multileader style, text
|
||||||
// style, arrowhead block, leader linetype) to display names.
|
// style, arrowhead block, leader linetype) into editable name
|
||||||
|
// pickers. The current handle resolves to a display name; the
|
||||||
|
// options list every candidate the drawing offers. Applying a
|
||||||
|
// pick resolves the name back to a handle in the update loop.
|
||||||
if let acadrust::EntityType::MultiLeader(ml) = entity {
|
if let acadrust::EntityType::MultiLeader(ml) = entity {
|
||||||
let doc = &self.tabs[i].scene.document;
|
let doc = &self.tabs[i].scene.document;
|
||||||
let mut set_named = |field: &str, name: String| {
|
// Option lists.
|
||||||
for section in sections.iter_mut() {
|
let mleader_styles: Vec<String> = doc
|
||||||
if let Some(row) =
|
.objects
|
||||||
section.props.iter_mut().find(|p| p.field == field)
|
.iter()
|
||||||
{
|
.filter_map(|(_, o)| match o {
|
||||||
row.value =
|
acadrust::objects::ObjectType::MultiLeaderStyle(s) => {
|
||||||
crate::scene::model::object::PropValue::ReadOnly(
|
|
||||||
name.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(h) = ml.style_handle {
|
|
||||||
if let Some(name) = doc.objects.iter().find_map(|(oh, o)| match o {
|
|
||||||
acadrust::objects::ObjectType::MultiLeaderStyle(s) if *oh == h => {
|
|
||||||
Some(s.name.clone())
|
Some(s.name.clone())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}) {
|
})
|
||||||
set_named("mleader_style", name);
|
.collect();
|
||||||
}
|
// A leader with no linetype handle draws ByBlock; expose that
|
||||||
}
|
// as the first option so the default is selectable.
|
||||||
if let Some(h) = ml.text_style_handle {
|
let ltype_names: Vec<String> = std::iter::once("ByBlock".to_string())
|
||||||
if let Some(name) = doc
|
.chain(
|
||||||
.text_styles
|
doc.line_types
|
||||||
.iter()
|
.iter()
|
||||||
.find(|s| s.handle == h)
|
.map(|l| l.name.clone())
|
||||||
.map(|s| s.name.clone())
|
.filter(|n| !n.is_empty()),
|
||||||
{
|
)
|
||||||
set_named("text_style_handle", name);
|
.collect();
|
||||||
}
|
// Arrowheads are blocks; the closed-filled default has no
|
||||||
}
|
// block, so seed the list with it and add the arrowhead
|
||||||
if let Some(h) = ml.arrowhead_handle {
|
// blocks (leading underscore) present in the drawing.
|
||||||
if let Some(name) = doc
|
let arrow_names: Vec<String> = std::iter::once("Closed filled".to_string())
|
||||||
.block_records
|
.chain(
|
||||||
.iter()
|
doc.block_records
|
||||||
.find(|b| b.handle == h)
|
.iter()
|
||||||
.map(|b| b.name.clone())
|
.filter(|b| b.name.starts_with('_'))
|
||||||
{
|
.map(|b| b.name.clone()),
|
||||||
set_named("arrowhead_handle", name);
|
)
|
||||||
}
|
.collect();
|
||||||
}
|
let tstyle_names = text_style_names.clone();
|
||||||
if let Some(h) = ml.line_type_handle {
|
// Currently selected names.
|
||||||
if let Some(name) = doc
|
let cur_style = ml
|
||||||
.line_types
|
.style_handle
|
||||||
.iter()
|
.and_then(|h| {
|
||||||
.find(|l| l.handle == h)
|
doc.objects.iter().find_map(|(oh, o)| match o {
|
||||||
.map(|l| l.name.clone())
|
acadrust::objects::ObjectType::MultiLeaderStyle(s)
|
||||||
{
|
if *oh == h =>
|
||||||
set_named("line_type_handle", name);
|
{
|
||||||
}
|
Some(s.name.clone())
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "Standard".to_string());
|
||||||
|
let cur_tstyle = ml
|
||||||
|
.text_style_handle
|
||||||
|
.and_then(|h| {
|
||||||
|
doc.text_styles.iter().find(|s| s.handle == h).map(|s| s.name.clone())
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "Standard".to_string());
|
||||||
|
let cur_arrow = ml
|
||||||
|
.arrowhead_handle
|
||||||
|
.and_then(|h| {
|
||||||
|
doc.block_records.iter().find(|b| b.handle == h).map(|b| b.name.clone())
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "Closed filled".to_string());
|
||||||
|
let cur_ltype = ml
|
||||||
|
.line_type_handle
|
||||||
|
.and_then(|h| {
|
||||||
|
doc.line_types.iter().find(|l| l.handle == h).map(|l| l.name.clone())
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "ByBlock".to_string());
|
||||||
|
let mut set_choice =
|
||||||
|
|field: &str, selected: String, options: Vec<String>| {
|
||||||
|
for section in sections.iter_mut() {
|
||||||
|
if let Some(row) =
|
||||||
|
section.props.iter_mut().find(|p| p.field == field)
|
||||||
|
{
|
||||||
|
row.value =
|
||||||
|
crate::scene::model::object::PropValue::Choice {
|
||||||
|
selected: selected.clone(),
|
||||||
|
options: options.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
set_choice("mleader_style", cur_style, mleader_styles);
|
||||||
|
set_choice("text_style_handle", cur_tstyle, tstyle_names);
|
||||||
|
set_choice("arrowhead_handle", cur_arrow, arrow_names);
|
||||||
|
set_choice("line_type_handle", cur_ltype, ltype_names);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject viewport-only properties that require doc access.
|
// Inject viewport-only properties that require doc access.
|
||||||
@@ -487,27 +569,42 @@ impl OpenCADStudio {
|
|||||||
// are walked from the entity's extension dictionary — both
|
// are walked from the entity's extension dictionary — both
|
||||||
// need the document, so they are resolved here.
|
// need the document, so they are resolved here.
|
||||||
{
|
{
|
||||||
let anno = match entity {
|
// Which entities show an Annotative row, the field it uses,
|
||||||
acadrust::EntityType::Text(t) => {
|
// and — for those that don't already carry the row
|
||||||
Some((text_style_annotative(doc, &t.style), "annotative"))
|
// (dimension / table) — the existing field to insert it
|
||||||
|
// after. MLeader uses its editable toggle field.
|
||||||
|
let anno: Option<(&str, Option<&str>)> = match entity {
|
||||||
|
acadrust::EntityType::Text(_)
|
||||||
|
| acadrust::EntityType::MText(_)
|
||||||
|
| acadrust::EntityType::Leader(_) => Some(("annotative", None)),
|
||||||
|
acadrust::EntityType::MultiLeader(_) => {
|
||||||
|
Some(("enable_annotation_scale", None))
|
||||||
}
|
}
|
||||||
acadrust::EntityType::MText(t) => Some((
|
acadrust::EntityType::Dimension(_) => {
|
||||||
t.is_annotative || text_style_annotative(doc, &t.style),
|
Some(("annotative", Some("style_name")))
|
||||||
"annotative",
|
}
|
||||||
)),
|
acadrust::EntityType::Table(_) => {
|
||||||
acadrust::EntityType::Leader(l) => {
|
Some(("annotative", Some("tbl_style_handle")))
|
||||||
Some((dim_style_annotative(doc, &l.dimension_style), "annotative"))
|
|
||||||
}
|
}
|
||||||
acadrust::EntityType::MultiLeader(ml) => Some((
|
|
||||||
ml.enable_annotation_scale
|
|
||||||
|| mleader_style_annotative(doc, ml.style_handle),
|
|
||||||
"enable_annotation_scale",
|
|
||||||
)),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some((is_anno, anno_field)) = anno {
|
if let Some((anno_field, insert_after)) = anno {
|
||||||
// MLeader keeps its editable toggle; the read-only
|
let is_anno = crate::scene::annotative::is_annotative(doc, entity);
|
||||||
// text rows get an explicit Yes/No.
|
// Dimensions/tables carry no Annotative row yet — add one
|
||||||
|
// right after their style row.
|
||||||
|
if let Some(anchor) = insert_after {
|
||||||
|
insert_row_after(
|
||||||
|
&mut sections,
|
||||||
|
anchor,
|
||||||
|
crate::entities::common::ro_prop(
|
||||||
|
"Annotative",
|
||||||
|
"annotative",
|
||||||
|
"No",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// The read-only text rows get an explicit Yes/No; MLeader
|
||||||
|
// keeps its editable toggle.
|
||||||
if anno_field == "annotative" {
|
if anno_field == "annotative" {
|
||||||
set_row(
|
set_row(
|
||||||
&mut sections,
|
&mut sections,
|
||||||
@@ -516,19 +613,16 @@ impl OpenCADStudio {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if is_anno {
|
if is_anno {
|
||||||
let names = entity_scale_names(doc, entity.common());
|
// The applied annotation scale follows the current
|
||||||
let display = if names.is_empty() {
|
// annotation scale (CANNOSCALE / the status-bar
|
||||||
doc.header.current_annotation_scale.clone()
|
// scale pill), not a per-object stored value.
|
||||||
} else {
|
|
||||||
names.join(", ")
|
|
||||||
};
|
|
||||||
insert_row_after(
|
insert_row_after(
|
||||||
&mut sections,
|
&mut sections,
|
||||||
anno_field,
|
anno_field,
|
||||||
crate::entities::common::ro_prop(
|
crate::entities::common::ro_prop(
|
||||||
"Annotative scale",
|
"Annotative scale",
|
||||||
"annotative_scale",
|
"annotative_scale",
|
||||||
display,
|
doc.header.current_annotation_scale.clone(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1243,93 +1337,6 @@ fn find_dim_style<'a>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the named text style annotative?
|
|
||||||
fn text_style_annotative(doc: &acadrust::CadDocument, name: &str) -> bool {
|
|
||||||
doc.text_styles
|
|
||||||
.iter()
|
|
||||||
.find(|s| {
|
|
||||||
s.name.eq_ignore_ascii_case(name)
|
|
||||||
|| (name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard"))
|
|
||||||
})
|
|
||||||
.map_or(false, |s| s.annotative)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the named dimension style annotative?
|
|
||||||
fn dim_style_annotative(doc: &acadrust::CadDocument, name: &str) -> bool {
|
|
||||||
find_dim_style(doc, name).map_or(false, |s| s.annotative)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the multileader style (by handle) annotative?
|
|
||||||
fn mleader_style_annotative(doc: &acadrust::CadDocument, handle: Option<acadrust::Handle>) -> bool {
|
|
||||||
let Some(h) = handle else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
doc.objects.iter().any(|(oh, o)| {
|
|
||||||
matches!(o, acadrust::objects::ObjectType::MultiLeaderStyle(s) if *oh == h && s.is_annotative)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve a handle to a `Dictionary` object, if it is one.
|
|
||||||
fn as_dict(
|
|
||||||
doc: &acadrust::CadDocument,
|
|
||||||
handle: acadrust::Handle,
|
|
||||||
) -> Option<&acadrust::objects::Dictionary> {
|
|
||||||
match doc.objects.get(&handle) {
|
|
||||||
Some(acadrust::objects::ObjectType::Dictionary(d)) => Some(d),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Case-insensitive dictionary entry lookup by key.
|
|
||||||
fn dict_get(dict: &acadrust::objects::Dictionary, key: &str) -> Option<acadrust::Handle> {
|
|
||||||
dict.entries
|
|
||||||
.iter()
|
|
||||||
.find(|(k, _)| k.eq_ignore_ascii_case(key))
|
|
||||||
.map(|(_, h)| *h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The AcDbScale object's ratio name (e.g. "1:50"), by handle.
|
|
||||||
fn scale_name(doc: &acadrust::CadDocument, handle: acadrust::Handle) -> Option<String> {
|
|
||||||
match doc.objects.get(&handle) {
|
|
||||||
Some(acadrust::objects::ObjectType::Scale(s)) => Some(s.name.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The annotation-scale name(s) assigned to an entity, walked from its extension
|
|
||||||
/// dictionary → AcDbContextDataManager → ACDB_ANNOTATIONSCALES.
|
|
||||||
///
|
|
||||||
/// The collection's entry keys are internal anonymous names (e.g. "*A1"); the
|
|
||||||
/// real scale is each leaf's group-340 SCALE handle, which acadrust surfaces via
|
|
||||||
/// `doc.context_scales` (context-leaf handle → AcDbScale handle). We resolve each
|
|
||||||
/// leaf handle to its scale, then the scale to its ratio name. Empty when the
|
|
||||||
/// entity has no per-scale context (it then uses the drawing's current scale).
|
|
||||||
fn entity_scale_names(
|
|
||||||
doc: &acadrust::CadDocument,
|
|
||||||
common: &acadrust::entities::EntityCommon,
|
|
||||||
) -> Vec<String> {
|
|
||||||
let Some(xd) = common.xdictionary_handle else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
let Some(coll) = as_dict(doc, xd)
|
|
||||||
.and_then(|d| dict_get(d, "AcDbContextDataManager"))
|
|
||||||
.and_then(|h| as_dict(doc, h))
|
|
||||||
.and_then(|m| dict_get(m, "ACDB_ANNOTATIONSCALES"))
|
|
||||||
.and_then(|h| as_dict(doc, h))
|
|
||||||
else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
let mut names: Vec<String> = Vec::new();
|
|
||||||
for (_, leaf) in &coll.entries {
|
|
||||||
if let Some(name) = doc.context_scales.get(leaf).and_then(|sh| scale_name(doc, *sh)) {
|
|
||||||
if !names.contains(&name) {
|
|
||||||
names.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
names
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert `row` immediately after the first property whose field matches.
|
/// Insert `row` immediately after the first property whose field matches.
|
||||||
fn insert_row_after(
|
fn insert_row_after(
|
||||||
sections: &mut [crate::scene::model::object::PropSection],
|
sections: &mut [crate::scene::model::object::PropSection],
|
||||||
|
|||||||
@@ -1243,6 +1243,158 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
}
|
}
|
||||||
self.tabs[i].scene.camera_generation += 1;
|
self.tabs[i].scene.camera_generation += 1;
|
||||||
}
|
}
|
||||||
|
} else if matches!(
|
||||||
|
field,
|
||||||
|
"mleader_style"
|
||||||
|
| "text_style_handle"
|
||||||
|
| "arrowhead_handle"
|
||||||
|
| "line_type_handle"
|
||||||
|
) {
|
||||||
|
// Resolve a picked name back to the handle the MLEADER
|
||||||
|
// stores. The style/text-style rows keep their existing
|
||||||
|
// handle on a failed lookup; the arrowhead/linetype rows
|
||||||
|
// take the resolved value directly (None = the default
|
||||||
|
// "Closed filled" / "ByBlock" option).
|
||||||
|
let doc = &self.tabs[i].scene.document;
|
||||||
|
let resolved: Option<acadrust::Handle> = match field {
|
||||||
|
"mleader_style" => doc.objects.iter().find_map(|(h, o)| match o {
|
||||||
|
acadrust::objects::ObjectType::MultiLeaderStyle(s)
|
||||||
|
if s.name == value =>
|
||||||
|
{
|
||||||
|
Some(*h)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
"text_style_handle" => doc
|
||||||
|
.text_styles
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == value)
|
||||||
|
.map(|s| s.handle),
|
||||||
|
"arrowhead_handle" => {
|
||||||
|
if value == "Closed filled" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
doc.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.name == value)
|
||||||
|
.map(|b| b.handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"line_type_handle" => {
|
||||||
|
if value == "ByBlock" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
doc.line_types
|
||||||
|
.iter()
|
||||||
|
.find(|l| l.name == value)
|
||||||
|
.map(|l| l.handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(acadrust::EntityType::MultiLeader(ml)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
match field {
|
||||||
|
"mleader_style" => {
|
||||||
|
if let Some(h) = resolved {
|
||||||
|
ml.style_handle = Some(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"text_style_handle" => {
|
||||||
|
if let Some(h) = resolved {
|
||||||
|
ml.text_style_handle = Some(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"arrowhead_handle" => ml.arrowhead_handle = resolved,
|
||||||
|
"line_type_handle" => ml.line_type_handle = resolved,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field == "plot_style" {
|
||||||
|
// Named plot-style pick: ByLayer / ByBlock clear the
|
||||||
|
// handle; a named style resolves through the drawing's
|
||||||
|
// ACAD_PLOTSTYLENAME dictionary to its placeholder handle.
|
||||||
|
let dict_h =
|
||||||
|
self.tabs[i].scene.document.header.acad_plotstylename_dict_handle;
|
||||||
|
let ph: Option<acadrust::Handle> =
|
||||||
|
crate::scene::annotative::as_dict(&self.tabs[i].scene.document, dict_h)
|
||||||
|
.and_then(|d| {
|
||||||
|
d.entries
|
||||||
|
.iter()
|
||||||
|
.find(|(n, _)| *n == value)
|
||||||
|
.map(|(_, h)| *h)
|
||||||
|
});
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
let common = entity.common_mut();
|
||||||
|
match value.as_str() {
|
||||||
|
"ByLayer" => {
|
||||||
|
common.plotstyle_flags = 0;
|
||||||
|
common.plotstyle_handle = None;
|
||||||
|
}
|
||||||
|
"ByBlock" => {
|
||||||
|
common.plotstyle_flags = 1;
|
||||||
|
common.plotstyle_handle = None;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(h) = ph {
|
||||||
|
common.plotstyle_flags = 3;
|
||||||
|
common.plotstyle_handle = Some(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field == "material" {
|
||||||
|
// Material source: ByLayer / ByBlock clear the handle; a
|
||||||
|
// named material sets flag 3 + its handle (resolved here
|
||||||
|
// because the update loop holds the document).
|
||||||
|
let mat_handle: Option<acadrust::Handle> = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.find_map(|(h, o)| match o {
|
||||||
|
acadrust::objects::ObjectType::Material(m) if m.name == value => {
|
||||||
|
Some(*h)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
let common = entity.common_mut();
|
||||||
|
match value.as_str() {
|
||||||
|
"ByLayer" => {
|
||||||
|
common.material_flags = 0;
|
||||||
|
common.material_handle = None;
|
||||||
|
}
|
||||||
|
"ByBlock" => {
|
||||||
|
common.material_flags = 1;
|
||||||
|
common.material_handle = None;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(h) = mat_handle {
|
||||||
|
common.material_flags = 3;
|
||||||
|
common.material_handle = Some(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for &handle in &handles {
|
for &handle in &handles {
|
||||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle)
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
|||||||
+41
-12
@@ -37,7 +37,7 @@ fn properties(ins: &Insert) -> Vec<PropSection> {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
vec![
|
let mut sections = vec![
|
||||||
PropSection {
|
PropSection {
|
||||||
title: "Geometry".into(),
|
title: "Geometry".into(),
|
||||||
props: vec![
|
props: vec![
|
||||||
@@ -54,24 +54,24 @@ fn properties(ins: &Insert) -> Vec<PropSection> {
|
|||||||
props: vec![
|
props: vec![
|
||||||
ro("Name", "block", ins.block_name.clone()),
|
ro("Name", "block", ins.block_name.clone()),
|
||||||
edit("Rotation", "rotation", ins.rotation.to_degrees()),
|
edit("Rotation", "rotation", ins.rotation.to_degrees()),
|
||||||
ro("Block Unit", "block_unit", String::new()),
|
|
||||||
ro("Unit factor", "unit_factor", String::new()),
|
|
||||||
ro(
|
ro(
|
||||||
"Annotative",
|
"Annotative",
|
||||||
"annotative",
|
"annotative",
|
||||||
if annotative { "Yes" } else { "No" },
|
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(),
|
title: "Attributes".into(),
|
||||||
props: attrs,
|
props: attrs,
|
||||||
},
|
});
|
||||||
PropSection {
|
}
|
||||||
title: "Custom".into(),
|
sections
|
||||||
props: Vec::new(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_geom_prop(ins: &mut Insert, field: &str, value: &str) {
|
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 {
|
if attmode == 0 {
|
||||||
return;
|
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 {
|
for attr in &ins.attributes {
|
||||||
let per_attr_hidden = attr.common.invisible || attr.flags.invisible;
|
let per_attr_hidden = attr.common.invisible || attr.flags.invisible;
|
||||||
if attmode == 1 && per_attr_hidden {
|
if attmode == 1 && per_attr_hidden {
|
||||||
continue;
|
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(
|
let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) = render::render_style_for_block_sub(
|
||||||
document,
|
document,
|
||||||
&attr_entity,
|
&attr_entity,
|
||||||
@@ -237,7 +264,9 @@ pub(crate) fn append_insert_attribute_wires(
|
|||||||
sub_plen * pslt_factor,
|
sub_plen * pslt_factor,
|
||||||
sub_pat.map(|v| v * pslt_factor),
|
sub_pat.map(|v| v * pslt_factor),
|
||||||
sub_lw_px,
|
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,
|
None,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
|
|||||||
+11
-7
@@ -367,6 +367,10 @@ pub fn tessellate_table(
|
|||||||
selected: bool,
|
selected: bool,
|
||||||
entity_color: [f32; 4],
|
entity_color: [f32; 4],
|
||||||
line_weight_px: f32,
|
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> {
|
) -> Vec<crate::scene::model::wire_model::WireModel> {
|
||||||
use crate::scene::convert::tess_util::aci_to_rgba;
|
use crate::scene::convert::tess_util::aci_to_rgba;
|
||||||
use crate::scene::model::wire_model::WireModel;
|
use crate::scene::model::wire_model::WireModel;
|
||||||
@@ -431,7 +435,7 @@ pub fn tessellate_table(
|
|||||||
let mut off = 0.0f32;
|
let mut off = 0.0f32;
|
||||||
let mut v = vec![0.0f32];
|
let mut v = vec![0.0f32];
|
||||||
for col in &tab.columns {
|
for col in &tab.columns {
|
||||||
off += col.width as f32;
|
off += col.width as f32 * anno_scale;
|
||||||
v.push(off);
|
v.push(off);
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
@@ -440,7 +444,7 @@ pub fn tessellate_table(
|
|||||||
let mut off = 0.0f32;
|
let mut off = 0.0f32;
|
||||||
let mut v = vec![0.0f32];
|
let mut v = vec![0.0f32];
|
||||||
for row in &tab.rows {
|
for row in &tab.rows {
|
||||||
off += row.height as f32;
|
off += row.height as f32 * anno_scale;
|
||||||
v.push(off);
|
v.push(off);
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
@@ -450,8 +454,8 @@ pub fn tessellate_table(
|
|||||||
let header_suppressed = table_style.map(|t| t.header_suppressed).unwrap_or(false);
|
let header_suppressed = table_style.map(|t| t.header_suppressed).unwrap_or(false);
|
||||||
let h_margin = table_style
|
let h_margin = table_style
|
||||||
.map(|t| t.horizontal_margin as f32)
|
.map(|t| t.horizontal_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);
|
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> {
|
let lookup_style = |hh: acadrust::Handle| -> Option<&acadrust::tables::TextStyle> {
|
||||||
document.text_styles.iter().find(|s| s.handle == hh)
|
document.text_styles.iter().find(|s| s.handle == hh)
|
||||||
@@ -526,7 +530,7 @@ pub fn tessellate_table(
|
|||||||
let row_bot = row_offsets
|
let row_bot = row_offsets
|
||||||
.get(ri + 1)
|
.get(ri + 1)
|
||||||
.copied()
|
.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_mid = (row_top + row_bot) * 0.5;
|
||||||
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style.map(|ts| {
|
let row_style: Option<&acadrust::objects::RowCellStyle> = table_style.map(|ts| {
|
||||||
let kind = match (title_suppressed, header_suppressed, ri) {
|
let kind = match (title_suppressed, header_suppressed, ri) {
|
||||||
@@ -544,7 +548,7 @@ pub fn tessellate_table(
|
|||||||
|
|
||||||
for (ci, cell) in row.cells.iter().enumerate() {
|
for (ci, cell) in row.cells.iter().enumerate() {
|
||||||
let col_left = col_offsets[ci];
|
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 col_right = col_left + col_width;
|
||||||
let tl = origin + h * col_left + v_flow * row_top;
|
let tl = origin + h * col_left + v_flow * row_top;
|
||||||
let tr = origin + h * col_right + 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))
|
.or_else(|| row_style.map(|s| s.text_height).filter(|h| *h > 1e-6))
|
||||||
.map(|h| h as f32)
|
.map(|h| h as f32)
|
||||||
.unwrap_or(0.18);
|
.unwrap_or(0.18) * anno_scale;
|
||||||
let m_h = if h_margin > 1e-6 {
|
let m_h = if h_margin > 1e-6 {
|
||||||
h_margin
|
h_margin
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
//! Shared annotative-object detection + annotation-scale resolution.
|
||||||
|
//!
|
||||||
|
//! Both the Properties panel (Annotative row / applied scale name) and the
|
||||||
|
//! tessellation bake (which scales annotative content by the current annotation
|
||||||
|
//! scale) must agree on *which* entities are annotative — so that logic lives
|
||||||
|
//! here, once. An entity is annotative if it carries a per-object annotation
|
||||||
|
//! context, the legacy annotative XDATA, or an annotative style.
|
||||||
|
|
||||||
|
use acadrust::entities::{EntityCommon, EntityType};
|
||||||
|
use acadrust::objects::{Dictionary, ObjectType};
|
||||||
|
use acadrust::{CadDocument, Handle};
|
||||||
|
|
||||||
|
/// Resolve a handle to a `Dictionary` object, if it is one.
|
||||||
|
pub fn as_dict(doc: &CadDocument, handle: Handle) -> Option<&Dictionary> {
|
||||||
|
match doc.objects.get(&handle) {
|
||||||
|
Some(ObjectType::Dictionary(d)) => Some(d),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does a style name resolve to `name` (or to "Standard" when `name` is blank)?
|
||||||
|
fn name_matches(style_name: &str, name: &str) -> bool {
|
||||||
|
style_name.eq_ignore_ascii_case(name)
|
||||||
|
|| (name.trim().is_empty() && style_name.eq_ignore_ascii_case("Standard"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_style_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||||
|
doc.text_styles
|
||||||
|
.iter()
|
||||||
|
.find(|s| name_matches(&s.name, name))
|
||||||
|
.is_some_and(|s| s.annotative)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dim_style_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||||
|
doc.dim_styles
|
||||||
|
.iter()
|
||||||
|
.find(|s| name_matches(&s.name, name))
|
||||||
|
.is_some_and(|s| s.annotative)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mleader_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
|
||||||
|
let Some(h) = handle else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
doc.objects.iter().any(|(oh, o)| {
|
||||||
|
matches!(o, ObjectType::MultiLeaderStyle(s) if *oh == h && s.is_annotative)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
|
||||||
|
let Some(h) = handle else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
doc.objects
|
||||||
|
.iter()
|
||||||
|
.any(|(oh, o)| matches!(o, ObjectType::TableStyle(s) if *oh == h && s.annotative))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an object carries a per-object annotation context — its extension
|
||||||
|
/// dictionary holds an `AcDbContextDataManager`. This catches objects that are
|
||||||
|
/// annotative by context even when their style is not.
|
||||||
|
fn has_context_manager(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||||
|
common
|
||||||
|
.xdictionary_handle
|
||||||
|
.and_then(|h| as_dict(doc, h))
|
||||||
|
.map(|d| {
|
||||||
|
d.entries
|
||||||
|
.iter()
|
||||||
|
.any(|(k, _)| k.eq_ignore_ascii_case("AcDbContextDataManager"))
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an entity participates in annotation scaling.
|
||||||
|
pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
|
||||||
|
// Per-object annotation context (works regardless of style).
|
||||||
|
if has_context_manager(doc, entity.common()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Legacy annotative XDATA markers.
|
||||||
|
let xd = &entity.common().extended_data;
|
||||||
|
if xd.get_record("AcAnnoPO").is_some() || xd.get_record("AcAnnotativeData").is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Annotative via the assigned style (or the entity's own flag).
|
||||||
|
match entity {
|
||||||
|
EntityType::Text(t) => text_style_annotative(doc, &t.style),
|
||||||
|
EntityType::MText(t) => t.is_annotative || text_style_annotative(doc, &t.style),
|
||||||
|
EntityType::Dimension(d) => dim_style_annotative(doc, &d.base().style_name),
|
||||||
|
EntityType::Leader(l) => dim_style_annotative(doc, &l.dimension_style),
|
||||||
|
EntityType::MultiLeader(ml) => {
|
||||||
|
ml.enable_annotation_scale || mleader_style_annotative(doc, ml.style_handle)
|
||||||
|
}
|
||||||
|
EntityType::Table(t) => table_style_annotative(doc, t.table_style_handle),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Vendored
+21
-1
@@ -591,9 +591,29 @@ pub fn expand_insert(
|
|||||||
// from native content.
|
// from native content.
|
||||||
is_xref: bool,
|
is_xref: bool,
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
|
// Current annotation scale. An annotative block scales as one uniform unit
|
||||||
|
// about its insertion point; a non-annotative block is unaffected.
|
||||||
|
anno_scale: f32,
|
||||||
) -> Option<Vec<WireModel>> {
|
) -> Option<Vec<WireModel>> {
|
||||||
let defn = cache.defn(&ins.block_name)?;
|
let defn = cache.defn(&ins.block_name)?;
|
||||||
let xform = ins.get_transform();
|
let mut xform = ins.get_transform();
|
||||||
|
// Annotative blocks (the flag lives on the block definition; the instance is
|
||||||
|
// marked with the AcAnnotativeData XDATA) scale as ONE uniform unit about
|
||||||
|
// their insertion point — internal geometry/text/attributes are carried by
|
||||||
|
// this transform, never scaled individually (which would double-scale).
|
||||||
|
if (anno_scale - 1.0).abs() > 1e-6
|
||||||
|
&& ins
|
||||||
|
.common
|
||||||
|
.extended_data
|
||||||
|
.get_record("AcAnnotativeData")
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
let p = ins.insert_point;
|
||||||
|
let scale_about_p = Transform::from_translation(Vector3::new(-p.x, -p.y, -p.z))
|
||||||
|
.then(&Transform::from_scale(anno_scale as f64))
|
||||||
|
.then(&Transform::from_translation(Vector3::new(p.x, p.y, p.z)));
|
||||||
|
xform = xform.then(&scale_about_p);
|
||||||
|
}
|
||||||
let name = ins_handle.value().to_string();
|
let name = ins_handle.value().to_string();
|
||||||
let mut batches = Batches::default();
|
let mut batches = Batches::default();
|
||||||
let mut visited: Vec<String> = Vec::with_capacity(8);
|
let mut visited: Vec<String> = Vec::with_capacity(8);
|
||||||
|
|||||||
@@ -438,8 +438,16 @@ pub(crate) fn tessellate_entity(
|
|||||||
// No baked block (e.g. a table created in-app) — synthesise coloured
|
// No baked block (e.g. a table created in-app) — synthesise coloured
|
||||||
// geometry from the rows + TableStyle so fills/colours/borders/margins
|
// geometry from the rows + TableStyle so fills/colours/borders/margins
|
||||||
// are honoured instead of the monochrome fallback.
|
// are honoured instead of the monochrome fallback.
|
||||||
|
// Annotative tables scale with the current annotation scale (their
|
||||||
|
// stored geometry is at paper size); non-annotative tables are already
|
||||||
|
// model-size, so pass 1.0.
|
||||||
|
let table_anno = if crate::scene::annotative::is_annotative(document, e) {
|
||||||
|
anno_scale
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
let mut wires = crate::entities::table::tessellate_table(
|
let mut wires = crate::entities::table::tessellate_table(
|
||||||
tab, document, sel, entity_color, line_weight_px,
|
tab, document, sel, entity_color, line_weight_px, table_anno,
|
||||||
);
|
);
|
||||||
if !wires.is_empty() {
|
if !wires.is_empty() {
|
||||||
let aabb = entity_aabb(e);
|
let aabb = entity_aabb(e);
|
||||||
@@ -516,6 +524,7 @@ pub(crate) fn tessellate_entity(
|
|||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
is_xref,
|
is_xref,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
anno_scale,
|
||||||
) {
|
) {
|
||||||
// XCLIP: if this INSERT carries an enabled spatial filter,
|
// XCLIP: if this INSERT carries an enabled spatial filter,
|
||||||
// clip the expanded block geometry to the boundary polygon so
|
// clip the expanded block geometry to the boundary polygon so
|
||||||
|
|||||||
@@ -103,26 +103,17 @@ pub fn tessellate(
|
|||||||
};
|
};
|
||||||
let name = handle.value().to_string();
|
let name = handle.value().to_string();
|
||||||
|
|
||||||
// Determine effective annotation scale for this entity.
|
// Determine the effective annotation scale for this entity.
|
||||||
//
|
//
|
||||||
// AutoCAD's R2007+ "annotative" system marks objects via extension-
|
// Only annotative entities are auto-scaled by the current annotation scale;
|
||||||
// dictionary records or "AcAnnoPO" / "AcAnnotativeData" xdata. Only
|
// everything else is manually pre-scaled (old convention with $DIMSCALE and
|
||||||
// entities so marked should be auto-scaled by the viewport's
|
// oversized text). Annotative-ness is resolved centrally from the entity's
|
||||||
// paper-scale; everything else is treated as manually pre-scaled
|
// per-object context, legacy XDATA, or annotative style (see
|
||||||
// (old DXF/DWG convention with $DIMSCALE and oversized text).
|
// `scene::annotative::is_annotative`) so the bake and the panel agree.
|
||||||
//
|
let anno_scale = if crate::scene::annotative::is_annotative(document, entity) {
|
||||||
// Default: NOT annotative (anno_scale = 1.0). Opt-in via explicit
|
anno_scale
|
||||||
// xdata marker. Files that mark every entity annotative are rare; the
|
} else {
|
||||||
// pre-R2007 manual-scale convention is far more common in field data.
|
1.0
|
||||||
let anno_scale = {
|
|
||||||
let xdata = &entity.common().extended_data;
|
|
||||||
let is_annotative = xdata.get_record("AcAnnoPO").is_some()
|
|
||||||
|| xdata.get_record("AcAnnotativeData").is_some();
|
|
||||||
if is_annotative {
|
|
||||||
anno_scale
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A HATCH is drawn as a fill by the hatch pipeline and highlighted via a
|
// A HATCH is drawn as a fill by the hatch pipeline and highlighted via a
|
||||||
|
|||||||
+7
-6
@@ -5,6 +5,7 @@
|
|||||||
// pick — hit-testing, selection, grips, spatial index, xclip
|
// pick — hit-testing, selection, grips, spatial index, xclip
|
||||||
// view — camera, transforms, viewport, render pipeline driver
|
// view — camera, transforms, viewport, render pipeline driver
|
||||||
// cache — block-definition and property caches
|
// cache — block-definition and property caches
|
||||||
|
pub mod annotative;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
@@ -1158,12 +1159,12 @@ impl Scene {
|
|||||||
} else {
|
} else {
|
||||||
self.paper_bg_color
|
self.paper_bg_color
|
||||||
};
|
};
|
||||||
let anno = if self.current_layout == "Model" {
|
// Block definitions are cached at block-local size (annotation scale
|
||||||
self.annotation_scale
|
// 1.0). An annotative block scales as ONE unit at the INSERT level, so
|
||||||
} else {
|
// its internal geometry / text / attributes must NOT be scaled
|
||||||
1.0
|
// individually (that would double-scale — AutoCAD even forbids
|
||||||
};
|
// annotative attributes inside annotative blocks for this reason).
|
||||||
let built = cache::block_cache::BlockCache::build(&self.document, anno, bg);
|
let built = cache::block_cache::BlockCache::build(&self.document, 1.0, bg);
|
||||||
let arc = Arc::new(built);
|
let arc = Arc::new(built);
|
||||||
*self.block_defn_cache.borrow_mut() = Some((self.block_epoch, Arc::clone(&arc)));
|
*self.block_defn_cache.borrow_mut() = Some((self.block_epoch, Arc::clone(&arc)));
|
||||||
arc
|
arc
|
||||||
|
|||||||
Reference in new issue
Block a user