fix(styles): apply active creation defaults

Resolve style properties before editors and previews open.
Use absolute annotation multipliers for style-driven objects.

Refs #401

Refs #431
This commit is contained in:
Hakan Seven committed 2026-08-03 16:43:58 +03:00
1 parent 53cf31f33d
commit de2707a23e
14 files changed
+676 -129

No files matched your search

+53 -3
View File
@@ -49,7 +49,11 @@ impl OpenCADStudio {
// quick leader-plus-annotation operation.
"LEADER" | "QLEADER" => {
use crate::modules::annotate::leader_cmd::LeaderCommand;
let new_cmd = LeaderCommand::new();
let defaults = crate::scene::creation_style::current_dimension_defaults(
&self.tabs[i].scene.document,
);
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
let new_cmd = LeaderCommand::with_defaults(defaults, multiplier);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
@@ -486,7 +490,31 @@ impl OpenCADStudio {
"MLEADER" => {
use crate::modules::annotate::mleader_cmd::MLeaderCommand;
let new_cmd = MLeaderCommand::new();
let name = self.tabs[i]
.scene
.document
.header
.current_mleader_style_name
.clone();
let style = self.tabs[i]
.scene
.document
.objects
.iter()
.find_map(|(handle, object)| match object {
acadrust::objects::ObjectType::MultiLeaderStyle(style)
if style.name.eq_ignore_ascii_case(&name) =>
{
let mut style = style.clone();
style.handle = *handle;
Some(style)
}
_ => None,
});
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
let new_cmd = style.map_or_else(MLeaderCommand::new, |style| {
MLeaderCommand::with_style(style, multiplier)
});
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
@@ -500,7 +528,29 @@ impl OpenCADStudio {
"TABLE" => {
use crate::modules::annotate::table_cmd::TableCommand;
let cmd = TableCommand::new();
let name = self.tabs[i]
.scene
.document
.header
.current_table_style_name
.clone();
let style = self.tabs[i]
.scene
.document
.objects
.iter()
.find_map(|(handle, object)| match object {
acadrust::objects::ObjectType::TableStyle(style)
if style.name.eq_ignore_ascii_case(&name) =>
{
Some((*handle, style.clone()))
}
_ => None,
});
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
let cmd = style.as_ref().map_or_else(TableCommand::new, |(handle, style)| {
TableCommand::with_style(*handle, style, multiplier)
});
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
+41 -5
View File
@@ -13,8 +13,28 @@ impl OpenCADStudio {
"MLINE" => {
use crate::modules::draw::draw::mline::MlineCommand;
let style = self.tabs[i].scene.document.header.multiline_style.clone();
let cmd_obj = MlineCommand::with_style(style);
let style_name = self.tabs[i].scene.document.header.multiline_style.clone();
let style = self.tabs[i]
.scene
.document
.objects
.iter()
.find_map(|(handle, object)| match object {
acadrust::objects::ObjectType::MLineStyle(style)
if style.name.eq_ignore_ascii_case(&style_name) =>
{
Some((*handle, style.elements.len()))
}
_ => None,
});
let (style_handle, element_count) = style
.map(|(handle, count)| (Some(handle), count))
.unwrap_or((None, 2));
let cmd_obj = MlineCommand::with_style(
style_name,
style_handle,
element_count,
);
self.command_line.push_info(&cmd_obj.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
}
@@ -47,7 +67,15 @@ impl OpenCADStudio {
"ATTDEF" => {
use crate::modules::draw::draw::attdef::AttdefCommand;
let cmd = AttdefCommand::new();
let defaults = crate::scene::creation_style::current_text_defaults(
&self.tabs[i].scene.document,
);
let cmd = AttdefCommand::with_text_defaults(
defaults.height,
defaults.style_name,
defaults.width_factor,
defaults.oblique_angle,
);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
@@ -1049,7 +1077,11 @@ impl OpenCADStudio {
// ── Annotate commands ──────────────────────────────────────────
"TEXT" => {
use crate::modules::annotate::text::TextCommand;
let new_cmd = TextCommand::new();
let height = crate::scene::creation_style::current_text_defaults(
&self.tabs[i].scene.document,
)
.height;
let new_cmd = TextCommand::with_height(height);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
@@ -1080,7 +1112,11 @@ impl OpenCADStudio {
"MTEXT" => {
use crate::modules::annotate::mtext::MTextCommand;
let new_cmd = MTextCommand::new();
let height = crate::scene::creation_style::current_text_defaults(
&self.tabs[i].scene.document,
)
.height;
let new_cmd = MTextCommand::with_height(height);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
+4 -68
View File
@@ -1848,74 +1848,10 @@ impl OpenCADStudio {
entity.common_mut().linetype_scale = celtscale;
}
// A new dimension inherits the document's current dimension style
// (DIMSTYLE) instead of staying at the entity "Standard" default. Only
// fill in when still at the default so an explicitly-styled dimension
// is preserved. See #92.
if let acadrust::EntityType::Dimension(ref mut d) = entity {
let cur = self.tabs[i]
.scene
.document
.header
.current_dimstyle_name
.clone();
let s = d.base().style_name.clone();
if (s.is_empty() || s.eq_ignore_ascii_case("Standard")) && !cur.is_empty() {
d.base_mut().style_name = cur;
}
}
// MultiLeader / Table inherit the document's current style (#92). These
// styles live in the objects dictionary, so resolve the current style
// name to its object handle. Left untouched when the command already
// assigned a style or no matching style object exists.
match &mut entity {
acadrust::EntityType::MultiLeader(ml) if ml.style_handle.is_none() => {
let name = self.tabs[i]
.scene
.document
.header
.current_mleader_style_name
.clone();
if !name.is_empty() {
let found =
self.tabs[i].scene.document.objects.iter().find_map(|(h, o)| match o {
acadrust::objects::ObjectType::MultiLeaderStyle(s)
if s.name.eq_ignore_ascii_case(&name) =>
{
Some((*h, s.clone()))
}
_ => None,
});
if let Some((h, s)) = found {
debug_assert_eq!(h, s.handle);
crate::scene::annotative::apply_mleader_style(ml, &s);
}
}
}
acadrust::EntityType::Table(t) if t.table_style_handle.is_none() => {
let name = self.tabs[i]
.scene
.document
.header
.current_table_style_name
.clone();
if !name.is_empty() {
t.table_style_handle =
self.tabs[i].scene.document.objects.iter().find_map(|(h, o)| {
match o {
acadrust::objects::ObjectType::TableStyle(s)
if s.name.eq_ignore_ascii_case(&name) =>
{
Some(*h)
}
_ => None,
}
});
}
}
_ => {}
}
crate::scene::creation_style::apply_current_creation_styles(
&self.tabs[i].scene.document,
&mut entity,
);
let text_style_annotative = match &entity {
acadrust::EntityType::Text(text) => {
+2 -3
View File
@@ -1581,9 +1581,8 @@ fn tessellate_dimension_inner(
}
let style = effective_style.as_ref();
// DIMSCALE rule:
// dimstyle.dimscale > 0 → final multiplier; ignore anno_scale.
// dimstyle.dimscale == 0 → annotative: use anno_scale (= 1/vp_scale).
// A positive style scale is fixed. A zero style scale uses the multiplier
// resolved by the scene for the current annotation or viewport context.
let dim_scale = style
.map(|s| {
if s.dimscale > 1e-6 {
+73 -9
View File
@@ -34,11 +34,36 @@ pub fn tool() -> ToolDef {
pub struct LeaderCommand {
verts: Vec<DVec3>,
ucs: Mat4,
dimension_style: String,
text_style: String,
text_height: f64,
display_scale: f64,
gap: f64,
arrow_size: f64,
annotative: bool,
}
impl LeaderCommand {
pub fn new() -> Self {
Self { verts: Vec::new(), ucs: Mat4::IDENTITY }
pub fn with_defaults(
defaults: crate::scene::creation_style::DimensionCreationDefaults,
annotation_multiplier: f64,
) -> Self {
let display_scale = if defaults.annotative {
annotation_multiplier
} else {
defaults.scale
};
Self {
verts: Vec::new(),
ucs: Mat4::IDENTITY,
dimension_style: defaults.style_name,
text_style: defaults.text_style_name,
text_height: defaults.text_height,
display_scale,
gap: defaults.gap,
arrow_size: defaults.arrow_size,
annotative: defaults.annotative,
}
}
}
@@ -74,9 +99,30 @@ impl CadCommand for LeaderCommand {
}
// Place the leader plus an empty MText annotation, link them, then open
// the in-place MText editor so the user types the annotation text.
let leader = build_leader(&self.verts, self.ucs);
let (anchor, attach) = annotation_anchor(&self.verts, leader.text_height, self.ucs);
let mtext = build_mtext("", anchor, leader.text_height, attach, self.ucs);
let leader = build_leader(
&self.verts,
self.ucs,
&self.dimension_style,
self.text_height,
self.gap,
self.arrow_size,
);
let displayed_height = self.text_height * self.display_scale;
let (anchor, attach) = annotation_anchor(&self.verts, displayed_height, self.ucs);
let mtext_height = if self.annotative {
self.text_height
} else {
displayed_height
};
let mtext = build_mtext(
"",
anchor,
mtext_height,
attach,
self.ucs,
&self.text_style,
self.annotative,
);
CmdResult::CommitManyAndEditText {
entities: vec![EntityType::Leader(leader), EntityType::MText(mtext)],
edit_index: 1,
@@ -93,7 +139,10 @@ impl CadCommand for LeaderCommand {
}
let mut pts: Vec<Vec3> = self.verts.iter().map(|p| p.as_vec3()).collect();
pts.push(pt.as_vec3());
Some(preview_wire(&pts))
Some(preview_wire(
&pts,
(self.arrow_size * self.display_scale) as f32,
))
}
}
@@ -103,10 +152,21 @@ fn dv3(p: DVec3) -> Vector3 {
Vector3::new(p.x, p.y, p.z)
}
fn build_leader(verts: &[DVec3], ucs: Mat4) -> Leader {
fn build_leader(
verts: &[DVec3],
ucs: Mat4,
dimension_style: &str,
text_height: f64,
gap: f64,
arrow_size: f64,
) -> Leader {
let mut l = Leader::from_vertices(verts.iter().map(|p| dv3(*p)).collect());
l.creation_type = LeaderCreationType::WithText;
l.hookline_enabled = true;
l.dimension_style = dimension_style.to_string();
l.text_height = text_height;
l.dimension_gap = gap;
l.arrow_size = arrow_size;
// The hookline / text read along the UCS X axis (DXF "horizontal direction
// for text"); the renderer uses it instead of world horizontal.
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
@@ -143,11 +203,15 @@ fn build_mtext(
height: f64,
attach: AttachmentPoint,
ucs: Mat4,
text_style: &str,
annotative: bool,
) -> MText {
let mut m = MText::new();
m.value = text.to_string();
m.insertion_point = dv3(pos);
m.height = height;
m.style = text_style.to_string();
m.is_annotative = annotative;
m.attachment_point = attach;
// Text reads along the UCS X axis.
let ux = ucs.transform_vector3(Vec3::X);
@@ -155,10 +219,10 @@ fn build_mtext(
m
}
fn preview_wire(pts: &[Vec3]) -> WireModel {
fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect();
if pts.len() >= 2 {
let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.5);
let [w1, w2] = arrowhead_wings(pts[0], pts[1], arrow_size);
points.push([f32::NAN; 3]);
points.push([w1.x, w1.y, w1.z]);
points.push([pts[0].x, pts[0].y, pts[0].z]);
+64 -15
View File
@@ -29,11 +29,35 @@ pub fn tool() -> ToolDef {
pub struct MLeaderCommand {
verts: Vec<DVec3>,
ucs: Mat4,
style: Option<acadrust::objects::MultiLeaderStyle>,
display_scale: f64,
}
impl MLeaderCommand {
pub fn new() -> Self {
Self { verts: Vec::new(), ucs: Mat4::IDENTITY }
Self {
verts: Vec::new(),
ucs: Mat4::IDENTITY,
style: None,
display_scale: 1.0,
}
}
pub fn with_style(
style: acadrust::objects::MultiLeaderStyle,
annotation_multiplier: f64,
) -> Self {
let display_scale = if style.is_annotative {
annotation_multiplier
} else {
style.scale_factor
};
Self {
verts: Vec::new(),
ucs: Mat4::IDENTITY,
style: Some(style),
display_scale,
}
}
}
@@ -69,7 +93,13 @@ impl CadCommand for MLeaderCommand {
}
// Place the leader with empty text, then open the in-place MText editor
// so the user types the annotation into the rich editor.
let ml = build_mleader("", &self.verts, self.ucs);
let ml = build_mleader(
"",
&self.verts,
self.ucs,
self.style.as_ref(),
self.display_scale,
);
CmdResult::CommitAndEditText(EntityType::MultiLeader(ml))
}
@@ -84,7 +114,12 @@ impl CadCommand for MLeaderCommand {
// Preview / rubber-band is GPU screen-space: downcast to f32 here.
let mut pts: Vec<Vec3> = self.verts.iter().map(|p| p.as_vec3()).collect();
pts.push(pt.as_vec3());
Some(preview_wire(&pts))
let arrow_size = self
.style
.as_ref()
.map_or(2.5, |style| style.arrowhead_size)
* self.display_scale;
Some(preview_wire(&pts, arrow_size as f32))
}
}
@@ -94,7 +129,13 @@ fn v3(p: DVec3) -> Vector3 {
Vector3::new(p.x, p.y, p.z)
}
fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader {
fn build_mleader(
text: &str,
verts: &[DVec3],
ucs: Mat4,
style: Option<&acadrust::objects::MultiLeaderStyle>,
display_scale: f64,
) -> MultiLeader {
// Last vertex = content/text location; remaining = leader line points
let (leader_pts, content_pt) = verts.split_at(verts.len() - 1);
let content_pt = content_pt[0];
@@ -103,14 +144,22 @@ fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader {
let content_v3 = v3(content_pt);
let mut ml = MultiLeader::with_text(text, content_v3, leader_v3);
if let Some(style) = style {
crate::scene::annotative::apply_mleader_style(&mut ml, style);
} else {
ml.text_height = 2.5;
ml.context.text_height = 2.5;
ml.arrowhead_size = 2.5;
ml.context.arrowhead_size = 2.5;
ml.dogleg_length = 2.5;
}
// Match Leader entity defaults
ml.text_height = 2.5;
ml.context.text_height = 2.5;
ml.arrowhead_size = 2.5;
ml.dogleg_length = 2.5;
const DOGLEG: f64 = 2.5;
let landing_distance = ml.dogleg_length * display_scale;
let landing_gap = ml.context.landing_gap * display_scale;
ml.context.scale_factor = display_scale;
ml.context.text_height = ml.text_height * display_scale;
ml.context.arrowhead_size = ml.arrowhead_size * display_scale;
ml.context.landing_gap = landing_gap;
// "Horizontal" landing + text run along the active UCS X axis (identity =
// world), so the annotation reads square to the user's coordinate system.
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
@@ -130,22 +179,22 @@ fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader {
// the text along the UCS X axis.
root.direction = Vector3::new(landing.x as f64, landing.y as f64, 0.0);
root.connection_point = content_v3;
root.landing_distance = DOGLEG;
root.landing_distance = landing_distance;
}
// Seed the text one landing-length past the leader end, on the side the
// user dragged toward, offset along the UCS X axis.
let off = landing * DOGLEG as f32;
let off = landing * (landing_distance + landing_gap) as f32;
ml.context.text_location =
Vector3::new(content_v3.x + off.x as f64, content_v3.y + off.y as f64, content_v3.z);
ml
}
fn preview_wire(pts: &[Vec3]) -> WireModel {
fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect();
if pts.len() >= 2 {
let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.5);
let [w1, w2] = arrowhead_wings(pts[0], pts[1], arrow_size);
points.push([f32::NAN; 3]);
points.push([w1.x, w1.y, w1.z]);
points.push([pts[0].x, pts[0].y, pts[0].z]);
+4 -2
View File
@@ -21,12 +21,14 @@ enum Step {
pub struct MTextCommand {
step: Step,
height: f64,
}
impl MTextCommand {
pub fn new() -> Self {
pub fn with_height(height: f64) -> Self {
Self {
step: Step::InsertPoint,
height,
}
}
}
@@ -48,7 +50,7 @@ impl CadCommand for MTextCommand {
pos: pt,
handle: None,
initial: String::new(),
height: 0.25,
height: self.height,
}
}
+40 -5
View File
@@ -41,12 +41,46 @@ enum Step {
pub struct TableCommand {
step: Step,
style_handle: Option<acadrust::Handle>,
column_width: f64,
row_height: f64,
preview_scale: f64,
}
impl TableCommand {
pub fn new() -> Self {
Self {
step: Step::Columns,
style_handle: None,
column_width: COL_WIDTH,
row_height: ROW_HEIGHT,
preview_scale: 1.0,
}
}
pub fn with_style(
style_handle: acadrust::Handle,
style: &acadrust::objects::TableStyle,
annotation_multiplier: f64,
) -> Self {
let text_height = style
.data_row_style
.text_height
.max(style.header_row_style.text_height)
.max(style.title_row_style.text_height)
.max(1.0e-6);
let row_height = text_height * 1.5 + style.vertical_margin * 2.0;
let column_width = text_height * 8.0 + style.horizontal_margin * 2.0;
Self {
step: Step::Columns,
style_handle: Some(style_handle),
column_width,
row_height,
preview_scale: if style.annotative {
annotation_multiplier
} else {
1.0
},
}
}
}
@@ -130,11 +164,12 @@ impl CadCommand for TableCommand {
fn on_point(&mut self, pt: DVec3) -> CmdResult {
if let Step::Insertion { cols, rows } = self.step {
let ins = Vector3::new(pt.x, pt.y, pt.z);
let table = TableBuilder::new(rows, cols)
let mut table = TableBuilder::new(rows, cols)
.at(ins)
.row_height(ROW_HEIGHT)
.column_width(COL_WIDTH)
.row_height(self.row_height)
.column_width(self.column_width)
.build();
table.table_style_handle = self.style_handle;
CmdResult::CommitAndExit(EntityType::Table(table))
} else {
CmdResult::NeedPoint
@@ -144,8 +179,8 @@ impl CadCommand for TableCommand {
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
if let Step::Insertion { cols, rows } = self.step {
// Preview: outline of the table bounding box.
let w = (cols as f32) * COL_WIDTH as f32;
let h = (rows as f32) * ROW_HEIGHT as f32;
let w = (cols as f32) * (self.column_width * self.preview_scale) as f32;
let h = (rows as f32) * (self.row_height * self.preview_scale) as f32;
let x = pt.x;
let y = pt.y;
let z = pt.z;
+4 -2
View File
@@ -21,12 +21,14 @@ enum Step {
pub struct TextCommand {
step: Step,
height: f64,
}
impl TextCommand {
pub fn new() -> Self {
pub fn with_height(height: f64) -> Self {
Self {
step: Step::InsertPoint,
height,
}
}
}
@@ -48,7 +50,7 @@ impl CadCommand for TextCommand {
pos: pt,
handle: None,
initial: String::new(),
height: 0.25,
height: self.height,
}
}
+16 -3
View File
@@ -33,15 +33,25 @@ enum Step {
pub struct AttdefCommand {
step: Step,
/// Text height in world units.
height: f64,
text_style: String,
width_factor: f64,
oblique_angle: f64,
}
impl AttdefCommand {
pub fn new() -> Self {
pub fn with_text_defaults(
height: f64,
text_style: String,
width_factor: f64,
oblique_angle: f64,
) -> Self {
Self {
step: Step::Tag,
height: 0.2,
height,
text_style,
width_factor,
oblique_angle,
}
}
}
@@ -130,6 +140,9 @@ impl CadCommand for AttdefCommand {
default_value: default.clone(),
insertion_point: Vector3::new(pt.x, pt.y, pt.z),
height: self.height,
text_style: self.text_style.clone(),
width_factor: self.width_factor,
oblique_angle: self.oblique_angle,
..Default::default()
};
attdef.common.layer = "0".into();
+44 -13
View File
@@ -19,6 +19,8 @@ pub struct MlineCommand {
scale: f64,
waiting_scale: bool,
style_name: String,
style_handle: Option<acadrust::Handle>,
style_element_count: usize,
}
impl MlineCommand {
@@ -29,15 +31,23 @@ impl MlineCommand {
scale: 1.0,
waiting_scale: false,
style_name: "Standard".into(),
style_handle: None,
style_element_count: 2,
}
}
pub fn with_style(style_name: impl Into<String>) -> Self {
pub fn with_style(
style_name: impl Into<String>,
style_handle: Option<acadrust::Handle>,
style_element_count: usize,
) -> Self {
Self {
points: vec![],
scale: 1.0,
waiting_scale: false,
style_name: style_name.into(),
style_handle,
style_element_count: style_element_count.max(1),
}
}
}
@@ -94,7 +104,14 @@ impl CadCommand for MlineCommand {
// Close command
if (up == "C" || up == "CLOSE") && self.points.len() >= 3 {
let entity = build_mline(&self.points, self.scale, true, &self.style_name);
let entity = build_mline(
&self.points,
self.scale,
true,
&self.style_name,
self.style_handle,
self.style_element_count,
);
return Some(CmdResult::CommitAndExit(entity));
}
@@ -126,7 +143,14 @@ impl CadCommand for MlineCommand {
if self.points.len() < 2 {
return CmdResult::Cancel;
}
let entity = build_mline(&self.points, self.scale, false, &self.style_name);
let entity = build_mline(
&self.points,
self.scale,
false,
&self.style_name,
self.style_handle,
self.style_element_count,
);
CmdResult::CommitAndExit(entity)
}
@@ -171,18 +195,25 @@ impl CadCommand for MlineCommand {
}
}
fn build_mline(pts: &[DVec3], scale: f64, closed: bool, style_name: &str) -> EntityType {
let verts: Vec<Vector3> = pts
.iter()
.map(|p| Vector3::new(p.x, p.y, p.z))
.collect();
let mut mline = if closed {
MLine::closed_from_points(&verts)
} else {
MLine::from_points(&verts)
};
fn build_mline(
pts: &[DVec3],
scale: f64,
closed: bool,
style_name: &str,
style_handle: Option<acadrust::Handle>,
style_element_count: usize,
) -> EntityType {
let mut mline = MLine::new();
mline.scale_factor = scale;
mline.style_name = style_name.to_string();
mline.style_handle = style_handle;
mline.style_element_count = style_element_count;
for point in pts {
mline.add_vertex(Vector3::new(point.x, point.y, point.z));
}
if closed {
mline.close();
}
EntityType::MLine(mline)
}
+50 -1
View File
@@ -639,10 +639,33 @@ pub fn effective_annotation_scale_for(
fallback: f32,
scale_handle: Option<Handle>,
) -> f32 {
if !is_annotative(doc, entity) {
let context_annotative = is_annotative(doc, entity);
let style_annotative = annotation_style_is_annotative(doc, entity);
if !context_annotative && !style_annotative {
return 1.0;
}
if matches!(
entity,
EntityType::Dimension(_)
| EntityType::Leader(_)
| EntityType::Tolerance(_)
| EntityType::Table(_)
) {
return fallback;
}
if style_annotative
&& matches!(
entity,
EntityType::Text(_)
| EntityType::MText(_)
| EntityType::AttributeEntity(_)
| EntityType::AttributeDefinition(_)
)
{
return fallback;
}
// Unlike MTEXT / DIMENSION contexts, an MLEADER context carries its
// already-scaled text height and overall scale factor. Keep the text
// height as stored; make `ml.scale_factor * anno_scale` resolve to the
@@ -1392,6 +1415,15 @@ fn mleader_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
})
}
fn table_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
let Some(handle) = handle else {
return false;
};
doc.objects.iter().any(|(object_handle, object)| {
matches!(object, ObjectType::TableStyle(style) if *object_handle == handle && style.annotative)
})
}
/// Whether an object carries a per-object annotation context with at least one
/// per-scale representation — its extension dictionary holds an
/// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is
@@ -1454,6 +1486,7 @@ pub fn annotation_style_is_annotative(doc: &CadDocument, entity: &EntityType) ->
EntityType::MultiLeader(leader) => {
mleader_style_annotative(doc, leader.style_handle)
}
EntityType::Table(table) => table_style_annotative(doc, table.table_style_handle),
_ => false,
}
}
@@ -1473,6 +1506,8 @@ pub fn apply_mleader_style(
entity.dogleg_length = style.landing_distance;
entity.arrowhead_handle = style.arrowhead_handle;
entity.arrowhead_size = style.arrowhead_size;
entity.context.arrowhead_size = style.arrowhead_size;
entity.context.landing_gap = style.landing_gap;
entity.text_style_handle = style.text_style_handle;
entity.text_color = style.text_color;
entity.text_frame = style.text_frame;
@@ -1509,6 +1544,20 @@ pub fn apply_mleader_style(
entity.context.block_content_scale = entity.block_scale;
entity.context.scale_factor = style.scale_factor;
entity.enable_annotation_scale = style.is_annotative;
for root in &mut entity.context.leader_roots {
root.landing_distance = style.landing_distance;
root.text_attachment_direction = entity.text_attachment_direction;
for line in &mut root.lines {
if line.override_flags.is_empty() {
line.path_type = entity.path_type;
line.line_color = style.line_color;
line.line_type_handle = style.line_type_handle;
line.line_weight = style.line_weight;
line.arrowhead_handle = style.arrowhead_handle;
line.arrowhead_size = style.arrowhead_size;
}
}
}
}
pub fn apply_mleader_style_to_object(
+258
View File
@@ -0,0 +1,258 @@
use acadrust::objects::ObjectType;
use acadrust::tables::{DimStyle, TextStyle};
use acadrust::{CadDocument, EntityType, Handle};
#[derive(Debug, Clone)]
pub struct TextCreationDefaults {
pub style_name: String,
pub height: f64,
pub width_factor: f64,
pub oblique_angle: f64,
}
#[derive(Debug, Clone)]
pub struct DimensionCreationDefaults {
pub style_name: String,
pub style_handle: Handle,
pub text_style_name: String,
pub text_height: f64,
pub gap: f64,
pub arrow_size: f64,
pub scale: f64,
pub annotative: bool,
}
fn named_text_style(doc: &CadDocument, name: &str) -> Option<TextStyle> {
doc.text_styles
.iter()
.find(|style| style.name.eq_ignore_ascii_case(name))
.or_else(|| {
doc.text_styles
.iter()
.find(|style| style.name.eq_ignore_ascii_case("Standard"))
})
.cloned()
}
fn named_dim_style(doc: &CadDocument, name: &str) -> Option<DimStyle> {
doc.dim_styles
.iter()
.find(|style| style.name.eq_ignore_ascii_case(name))
.or_else(|| {
doc.dim_styles
.iter()
.find(|style| style.name.eq_ignore_ascii_case("Standard"))
})
.cloned()
}
pub fn current_text_defaults(doc: &CadDocument) -> TextCreationDefaults {
let requested = doc.header.current_text_style_name.trim();
let style = named_text_style(doc, requested);
let variable_height = if doc.header.text_height > 1.0e-9 {
doc.header.text_height
} else {
style
.as_ref()
.map(|style| style.last_height)
.filter(|height| *height > 1.0e-9)
.unwrap_or(2.5)
};
TextCreationDefaults {
style_name: style
.as_ref()
.map(|style| style.name.clone())
.filter(|name| !name.trim().is_empty())
.unwrap_or_else(|| "Standard".to_string()),
height: style
.as_ref()
.map(|style| style.height)
.filter(|height| *height > 1.0e-9)
.unwrap_or(variable_height),
width_factor: style.as_ref().map_or(1.0, |style| style.width_factor),
oblique_angle: style.as_ref().map_or(0.0, |style| style.oblique_angle),
}
}
pub fn current_dimension_defaults(doc: &CadDocument) -> DimensionCreationDefaults {
let requested = doc.header.current_dimstyle_name.trim();
let style = named_dim_style(doc, requested);
DimensionCreationDefaults {
style_name: style
.as_ref()
.map(|style| style.name.clone())
.filter(|name| !name.trim().is_empty())
.unwrap_or_else(|| "Standard".to_string()),
style_handle: style.as_ref().map_or(Handle::NULL, |style| style.handle),
text_style_name: style
.as_ref()
.map(|style| style.dimtxsty.clone())
.filter(|name| !name.trim().is_empty())
.unwrap_or_else(|| current_text_defaults(doc).style_name),
text_height: style.as_ref().map_or(2.5, |style| style.dimtxt),
gap: style.as_ref().map_or(0.625, |style| style.dimgap),
arrow_size: style.as_ref().map_or(2.5, |style| style.dimasz),
scale: style.as_ref().map_or(1.0, |style| {
if style.annotative {
1.0
} else if style.dimscale > 1.0e-9 {
style.dimscale
} else {
1.0
}
}),
annotative: style.as_ref().is_some_and(|style| style.annotative),
}
}
fn entity_uses_default_style(name: &str) -> bool {
name.trim().is_empty() || name.eq_ignore_ascii_case("Standard")
}
fn apply_text_defaults(doc: &CadDocument, entity: &mut EntityType) {
let active = current_text_defaults(doc);
let style_name = match entity {
EntityType::Text(text) => &mut text.style,
EntityType::MText(text) => &mut text.style,
EntityType::AttributeEntity(attribute) => &mut attribute.text_style,
EntityType::AttributeDefinition(attribute) => &mut attribute.text_style,
_ => return,
};
if style_name.trim().is_empty() {
style_name.clone_from(&active.style_name);
}
let resolved = named_text_style(doc, style_name)
.unwrap_or_else(|| TextStyle::new(style_name.clone()));
match entity {
EntityType::Text(text) => {
if resolved.height > 1.0e-9 {
text.height = resolved.height;
}
if (text.width_factor - 1.0).abs() <= 1.0e-9 {
text.width_factor = resolved.width_factor;
}
if text.oblique_angle.abs() <= 1.0e-9 {
text.oblique_angle = resolved.oblique_angle;
}
if text.generation_flags == 0 {
text.generation_flags = (if resolved.flags.backward { 2 } else { 0 })
| (if resolved.flags.upside_down { 4 } else { 0 });
}
}
EntityType::MText(text) => {
if resolved.height > 1.0e-9 {
text.height = resolved.height;
}
}
EntityType::AttributeEntity(attribute) => {
if resolved.height > 1.0e-9 {
attribute.height = resolved.height;
}
if (attribute.width_factor - 1.0).abs() <= 1.0e-9 {
attribute.width_factor = resolved.width_factor;
}
if attribute.oblique_angle.abs() <= 1.0e-9 {
attribute.oblique_angle = resolved.oblique_angle;
}
if attribute.text_generation_flags == 0 {
attribute.text_generation_flags =
(if resolved.flags.backward { 2 } else { 0 })
| (if resolved.flags.upside_down { 4 } else { 0 });
}
}
EntityType::AttributeDefinition(attribute) => {
if resolved.height > 1.0e-9 {
attribute.height = resolved.height;
}
if (attribute.width_factor - 1.0).abs() <= 1.0e-9 {
attribute.width_factor = resolved.width_factor;
}
if attribute.oblique_angle.abs() <= 1.0e-9 {
attribute.oblique_angle = resolved.oblique_angle;
}
if attribute.text_generation_flags == 0 {
attribute.text_generation_flags =
(if resolved.flags.backward { 2 } else { 0 })
| (if resolved.flags.upside_down { 4 } else { 0 });
}
}
_ => {}
}
}
fn apply_dimension_defaults(doc: &CadDocument, entity: &mut EntityType) {
let active = current_dimension_defaults(doc);
match entity {
EntityType::Dimension(dimension) => {
if entity_uses_default_style(&dimension.base().style_name) {
dimension.base_mut().style_name.clone_from(&active.style_name);
}
}
EntityType::Leader(leader) => {
if entity_uses_default_style(&leader.dimension_style) {
leader.dimension_style.clone_from(&active.style_name);
leader.text_height = active.text_height;
leader.dimension_gap = active.gap;
leader.arrow_size = active.arrow_size;
}
}
EntityType::Tolerance(tolerance) => {
if entity_uses_default_style(&tolerance.dimension_style_name) {
tolerance.dimension_style_name.clone_from(&active.style_name);
tolerance.dimension_style_handle = (!active.style_handle.is_null())
.then_some(active.style_handle);
tolerance.text_height = active.text_height;
tolerance.dimension_gap = active.gap;
}
}
_ => {}
}
}
fn apply_object_defaults(doc: &CadDocument, entity: &mut EntityType) {
match entity {
EntityType::MultiLeader(leader) if leader.style_handle.is_none() => {
let name = doc.header.current_mleader_style_name.trim();
if let Some(style) = doc.objects.iter().find_map(|(handle, object)| match object {
ObjectType::MultiLeaderStyle(style) if style.name.eq_ignore_ascii_case(name) => {
let mut style = style.clone();
style.handle = *handle;
Some(style)
}
_ => None,
}) {
crate::scene::annotative::apply_mleader_style(leader, &style);
}
}
EntityType::Table(table) if table.table_style_handle.is_none() => {
let name = doc.header.current_table_style_name.trim();
table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| match object {
ObjectType::TableStyle(style) if style.name.eq_ignore_ascii_case(name) => {
Some(*handle)
}
_ => None,
});
}
EntityType::MLine(mline) if mline.style_handle.is_none() => {
let name = doc.header.multiline_style.trim();
if let Some((handle, style_name)) =
doc.objects.iter().find_map(|(handle, object)| match object {
ObjectType::MLineStyle(style) if style.name.eq_ignore_ascii_case(name) => {
Some((*handle, style.name.clone()))
}
_ => None,
})
{
mline.style_handle = Some(handle);
mline.style_name = style_name;
}
}
_ => {}
}
}
pub fn apply_current_creation_styles(doc: &CadDocument, entity: &mut EntityType) {
apply_text_defaults(doc, entity);
apply_dimension_defaults(doc, entity);
apply_object_defaults(doc, entity);
}
+23
View File
@@ -8,6 +8,7 @@
pub mod annotative;
pub mod cache;
pub mod convert;
pub mod creation_style;
pub mod model;
pub mod pick;
pub mod pipeline;
@@ -3800,6 +3801,28 @@ impl Scene {
self.scale_handle_ensuring("1:1")
}
pub(crate) fn creation_annotation_multiplier(&self) -> f64 {
if self.current_layout == "Model" {
return self.annotation_scale.max(1.0e-9) as f64;
}
let Some(handle) = self.active_viewport else {
return 1.0;
};
let Some(EntityType::Viewport(viewport)) = self.document.get_entity(handle) else {
return 1.0;
};
let paper_to_model = vp_effective_scale(
viewport.custom_scale,
viewport.view_height,
viewport.height,
);
if paper_to_model.is_finite() && paper_to_model > 1.0e-9 {
1.0 / paper_to_model
} else {
1.0
}
}
pub fn set_annotation_scale_named(&mut self, name: &str) -> Option<Handle> {
let handle = self.scale_handle_ensuring(name)?;
let ObjectType::Scale(scale) = self.document.objects.get(&handle)? else {