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:
parent
53cf31f33d
commit
de2707a23e
14 changed files with 676 additions and 129 deletions
|
|
@ -49,7 +49,11 @@ impl OpenCADStudio {
|
||||||
// quick leader-plus-annotation operation.
|
// quick leader-plus-annotation operation.
|
||||||
"LEADER" | "QLEADER" => {
|
"LEADER" | "QLEADER" => {
|
||||||
use crate::modules::annotate::leader_cmd::LeaderCommand;
|
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.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
@ -486,7 +490,31 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
"MLEADER" => {
|
"MLEADER" => {
|
||||||
use crate::modules::annotate::mleader_cmd::MLeaderCommand;
|
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.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
@ -500,7 +528,29 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
"TABLE" => {
|
"TABLE" => {
|
||||||
use crate::modules::annotate::table_cmd::TableCommand;
|
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.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,28 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
"MLINE" => {
|
"MLINE" => {
|
||||||
use crate::modules::draw::draw::mline::MlineCommand;
|
use crate::modules::draw::draw::mline::MlineCommand;
|
||||||
let style = self.tabs[i].scene.document.header.multiline_style.clone();
|
let style_name = self.tabs[i].scene.document.header.multiline_style.clone();
|
||||||
let cmd_obj = MlineCommand::with_style(style);
|
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.command_line.push_info(&cmd_obj.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +67,15 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
"ATTDEF" => {
|
"ATTDEF" => {
|
||||||
use crate::modules::draw::draw::attdef::AttdefCommand;
|
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.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
|
|
@ -1049,7 +1077,11 @@ impl OpenCADStudio {
|
||||||
// ── Annotate commands ──────────────────────────────────────────
|
// ── Annotate commands ──────────────────────────────────────────
|
||||||
"TEXT" => {
|
"TEXT" => {
|
||||||
use crate::modules::annotate::text::TextCommand;
|
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.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
@ -1080,7 +1112,11 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
"MTEXT" => {
|
"MTEXT" => {
|
||||||
use crate::modules::annotate::mtext::MTextCommand;
|
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.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1848,74 +1848,10 @@ impl OpenCADStudio {
|
||||||
entity.common_mut().linetype_scale = celtscale;
|
entity.common_mut().linetype_scale = celtscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A new dimension inherits the document's current dimension style
|
crate::scene::creation_style::apply_current_creation_styles(
|
||||||
// (DIMSTYLE) instead of staying at the entity "Standard" default. Only
|
&self.tabs[i].scene.document,
|
||||||
// fill in when still at the default so an explicitly-styled dimension
|
&mut entity,
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let text_style_annotative = match &entity {
|
let text_style_annotative = match &entity {
|
||||||
acadrust::EntityType::Text(text) => {
|
acadrust::EntityType::Text(text) => {
|
||||||
|
|
|
||||||
|
|
@ -1581,9 +1581,8 @@ fn tessellate_dimension_inner(
|
||||||
}
|
}
|
||||||
let style = effective_style.as_ref();
|
let style = effective_style.as_ref();
|
||||||
|
|
||||||
// DIMSCALE rule:
|
// A positive style scale is fixed. A zero style scale uses the multiplier
|
||||||
// dimstyle.dimscale > 0 → final multiplier; ignore anno_scale.
|
// resolved by the scene for the current annotation or viewport context.
|
||||||
// dimstyle.dimscale == 0 → annotative: use anno_scale (= 1/vp_scale).
|
|
||||||
let dim_scale = style
|
let dim_scale = style
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
if s.dimscale > 1e-6 {
|
if s.dimscale > 1e-6 {
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,36 @@ pub fn tool() -> ToolDef {
|
||||||
pub struct LeaderCommand {
|
pub struct LeaderCommand {
|
||||||
verts: Vec<DVec3>,
|
verts: Vec<DVec3>,
|
||||||
ucs: Mat4,
|
ucs: Mat4,
|
||||||
|
dimension_style: String,
|
||||||
|
text_style: String,
|
||||||
|
text_height: f64,
|
||||||
|
display_scale: f64,
|
||||||
|
gap: f64,
|
||||||
|
arrow_size: f64,
|
||||||
|
annotative: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LeaderCommand {
|
impl LeaderCommand {
|
||||||
pub fn new() -> Self {
|
pub fn with_defaults(
|
||||||
Self { verts: Vec::new(), ucs: Mat4::IDENTITY }
|
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
|
// Place the leader plus an empty MText annotation, link them, then open
|
||||||
// the in-place MText editor so the user types the annotation text.
|
// the in-place MText editor so the user types the annotation text.
|
||||||
let leader = build_leader(&self.verts, self.ucs);
|
let leader = build_leader(
|
||||||
let (anchor, attach) = annotation_anchor(&self.verts, leader.text_height, self.ucs);
|
&self.verts,
|
||||||
let mtext = build_mtext("", anchor, leader.text_height, attach, self.ucs);
|
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 {
|
CmdResult::CommitManyAndEditText {
|
||||||
entities: vec![EntityType::Leader(leader), EntityType::MText(mtext)],
|
entities: vec![EntityType::Leader(leader), EntityType::MText(mtext)],
|
||||||
edit_index: 1,
|
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();
|
let mut pts: Vec<Vec3> = self.verts.iter().map(|p| p.as_vec3()).collect();
|
||||||
pts.push(pt.as_vec3());
|
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)
|
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());
|
let mut l = Leader::from_vertices(verts.iter().map(|p| dv3(*p)).collect());
|
||||||
l.creation_type = LeaderCreationType::WithText;
|
l.creation_type = LeaderCreationType::WithText;
|
||||||
l.hookline_enabled = true;
|
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
|
// The hookline / text read along the UCS X axis (DXF "horizontal direction
|
||||||
// for text"); the renderer uses it instead of world horizontal.
|
// for text"); the renderer uses it instead of world horizontal.
|
||||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
|
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
|
||||||
|
|
@ -143,11 +203,15 @@ fn build_mtext(
|
||||||
height: f64,
|
height: f64,
|
||||||
attach: AttachmentPoint,
|
attach: AttachmentPoint,
|
||||||
ucs: Mat4,
|
ucs: Mat4,
|
||||||
|
text_style: &str,
|
||||||
|
annotative: bool,
|
||||||
) -> MText {
|
) -> MText {
|
||||||
let mut m = MText::new();
|
let mut m = MText::new();
|
||||||
m.value = text.to_string();
|
m.value = text.to_string();
|
||||||
m.insertion_point = dv3(pos);
|
m.insertion_point = dv3(pos);
|
||||||
m.height = height;
|
m.height = height;
|
||||||
|
m.style = text_style.to_string();
|
||||||
|
m.is_annotative = annotative;
|
||||||
m.attachment_point = attach;
|
m.attachment_point = attach;
|
||||||
// Text reads along the UCS X axis.
|
// Text reads along the UCS X axis.
|
||||||
let ux = ucs.transform_vector3(Vec3::X);
|
let ux = ucs.transform_vector3(Vec3::X);
|
||||||
|
|
@ -155,10 +219,10 @@ fn build_mtext(
|
||||||
m
|
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();
|
let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||||
if pts.len() >= 2 {
|
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([f32::NAN; 3]);
|
||||||
points.push([w1.x, w1.y, w1.z]);
|
points.push([w1.x, w1.y, w1.z]);
|
||||||
points.push([pts[0].x, pts[0].y, pts[0].z]);
|
points.push([pts[0].x, pts[0].y, pts[0].z]);
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,35 @@ pub fn tool() -> ToolDef {
|
||||||
pub struct MLeaderCommand {
|
pub struct MLeaderCommand {
|
||||||
verts: Vec<DVec3>,
|
verts: Vec<DVec3>,
|
||||||
ucs: Mat4,
|
ucs: Mat4,
|
||||||
|
style: Option<acadrust::objects::MultiLeaderStyle>,
|
||||||
|
display_scale: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MLeaderCommand {
|
impl MLeaderCommand {
|
||||||
pub fn new() -> Self {
|
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
|
// Place the leader with empty text, then open the in-place MText editor
|
||||||
// so the user types the annotation into the rich 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))
|
CmdResult::CommitAndEditText(EntityType::MultiLeader(ml))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +114,12 @@ impl CadCommand for MLeaderCommand {
|
||||||
// Preview / rubber-band is GPU screen-space: downcast to f32 here.
|
// 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();
|
let mut pts: Vec<Vec3> = self.verts.iter().map(|p| p.as_vec3()).collect();
|
||||||
pts.push(pt.as_vec3());
|
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)
|
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
|
// Last vertex = content/text location; remaining = leader line points
|
||||||
let (leader_pts, content_pt) = verts.split_at(verts.len() - 1);
|
let (leader_pts, content_pt) = verts.split_at(verts.len() - 1);
|
||||||
let content_pt = content_pt[0];
|
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 content_v3 = v3(content_pt);
|
||||||
|
|
||||||
let mut ml = MultiLeader::with_text(text, content_v3, leader_v3);
|
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
|
let landing_distance = ml.dogleg_length * display_scale;
|
||||||
ml.text_height = 2.5;
|
let landing_gap = ml.context.landing_gap * display_scale;
|
||||||
ml.context.text_height = 2.5;
|
ml.context.scale_factor = display_scale;
|
||||||
ml.arrowhead_size = 2.5;
|
ml.context.text_height = ml.text_height * display_scale;
|
||||||
ml.dogleg_length = 2.5;
|
ml.context.arrowhead_size = ml.arrowhead_size * display_scale;
|
||||||
|
ml.context.landing_gap = landing_gap;
|
||||||
const DOGLEG: f64 = 2.5;
|
|
||||||
// "Horizontal" landing + text run along the active UCS X axis (identity =
|
// "Horizontal" landing + text run along the active UCS X axis (identity =
|
||||||
// world), so the annotation reads square to the user's coordinate system.
|
// world), so the annotation reads square to the user's coordinate system.
|
||||||
let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X);
|
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.
|
// the text along the UCS X axis.
|
||||||
root.direction = Vector3::new(landing.x as f64, landing.y as f64, 0.0);
|
root.direction = Vector3::new(landing.x as f64, landing.y as f64, 0.0);
|
||||||
root.connection_point = content_v3;
|
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
|
// Seed the text one landing-length past the leader end, on the side the
|
||||||
// user dragged toward, offset along the UCS X axis.
|
// 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 =
|
ml.context.text_location =
|
||||||
Vector3::new(content_v3.x + off.x as f64, content_v3.y + off.y as f64, content_v3.z);
|
Vector3::new(content_v3.x + off.x as f64, content_v3.y + off.y as f64, content_v3.z);
|
||||||
|
|
||||||
ml
|
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();
|
let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||||
if pts.len() >= 2 {
|
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([f32::NAN; 3]);
|
||||||
points.push([w1.x, w1.y, w1.z]);
|
points.push([w1.x, w1.y, w1.z]);
|
||||||
points.push([pts[0].x, pts[0].y, pts[0].z]);
|
points.push([pts[0].x, pts[0].y, pts[0].z]);
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,14 @@ enum Step {
|
||||||
|
|
||||||
pub struct MTextCommand {
|
pub struct MTextCommand {
|
||||||
step: Step,
|
step: Step,
|
||||||
|
height: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MTextCommand {
|
impl MTextCommand {
|
||||||
pub fn new() -> Self {
|
pub fn with_height(height: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
step: Step::InsertPoint,
|
step: Step::InsertPoint,
|
||||||
|
height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +50,7 @@ impl CadCommand for MTextCommand {
|
||||||
pos: pt,
|
pos: pt,
|
||||||
handle: None,
|
handle: None,
|
||||||
initial: String::new(),
|
initial: String::new(),
|
||||||
height: 0.25,
|
height: self.height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,46 @@ enum Step {
|
||||||
|
|
||||||
pub struct TableCommand {
|
pub struct TableCommand {
|
||||||
step: Step,
|
step: Step,
|
||||||
|
style_handle: Option<acadrust::Handle>,
|
||||||
|
column_width: f64,
|
||||||
|
row_height: f64,
|
||||||
|
preview_scale: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableCommand {
|
impl TableCommand {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
step: Step::Columns,
|
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 {
|
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||||
if let Step::Insertion { cols, rows } = self.step {
|
if let Step::Insertion { cols, rows } = self.step {
|
||||||
let ins = Vector3::new(pt.x, pt.y, pt.z);
|
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)
|
.at(ins)
|
||||||
.row_height(ROW_HEIGHT)
|
.row_height(self.row_height)
|
||||||
.column_width(COL_WIDTH)
|
.column_width(self.column_width)
|
||||||
.build();
|
.build();
|
||||||
|
table.table_style_handle = self.style_handle;
|
||||||
CmdResult::CommitAndExit(EntityType::Table(table))
|
CmdResult::CommitAndExit(EntityType::Table(table))
|
||||||
} else {
|
} else {
|
||||||
CmdResult::NeedPoint
|
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();
|
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||||||
if let Step::Insertion { cols, rows } = self.step {
|
if let Step::Insertion { cols, rows } = self.step {
|
||||||
// Preview: outline of the table bounding box.
|
// Preview: outline of the table bounding box.
|
||||||
let w = (cols as f32) * COL_WIDTH as f32;
|
let w = (cols as f32) * (self.column_width * self.preview_scale) as f32;
|
||||||
let h = (rows as f32) * ROW_HEIGHT as f32;
|
let h = (rows as f32) * (self.row_height * self.preview_scale) as f32;
|
||||||
let x = pt.x;
|
let x = pt.x;
|
||||||
let y = pt.y;
|
let y = pt.y;
|
||||||
let z = pt.z;
|
let z = pt.z;
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,14 @@ enum Step {
|
||||||
|
|
||||||
pub struct TextCommand {
|
pub struct TextCommand {
|
||||||
step: Step,
|
step: Step,
|
||||||
|
height: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextCommand {
|
impl TextCommand {
|
||||||
pub fn new() -> Self {
|
pub fn with_height(height: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
step: Step::InsertPoint,
|
step: Step::InsertPoint,
|
||||||
|
height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +50,7 @@ impl CadCommand for TextCommand {
|
||||||
pos: pt,
|
pos: pt,
|
||||||
handle: None,
|
handle: None,
|
||||||
initial: String::new(),
|
initial: String::new(),
|
||||||
height: 0.25,
|
height: self.height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,25 @@ enum Step {
|
||||||
|
|
||||||
pub struct AttdefCommand {
|
pub struct AttdefCommand {
|
||||||
step: Step,
|
step: Step,
|
||||||
/// Text height in world units.
|
|
||||||
height: f64,
|
height: f64,
|
||||||
|
text_style: String,
|
||||||
|
width_factor: f64,
|
||||||
|
oblique_angle: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttdefCommand {
|
impl AttdefCommand {
|
||||||
pub fn new() -> Self {
|
pub fn with_text_defaults(
|
||||||
|
height: f64,
|
||||||
|
text_style: String,
|
||||||
|
width_factor: f64,
|
||||||
|
oblique_angle: f64,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
step: Step::Tag,
|
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(),
|
default_value: default.clone(),
|
||||||
insertion_point: Vector3::new(pt.x, pt.y, pt.z),
|
insertion_point: Vector3::new(pt.x, pt.y, pt.z),
|
||||||
height: self.height,
|
height: self.height,
|
||||||
|
text_style: self.text_style.clone(),
|
||||||
|
width_factor: self.width_factor,
|
||||||
|
oblique_angle: self.oblique_angle,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
attdef.common.layer = "0".into();
|
attdef.common.layer = "0".into();
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ pub struct MlineCommand {
|
||||||
scale: f64,
|
scale: f64,
|
||||||
waiting_scale: bool,
|
waiting_scale: bool,
|
||||||
style_name: String,
|
style_name: String,
|
||||||
|
style_handle: Option<acadrust::Handle>,
|
||||||
|
style_element_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MlineCommand {
|
impl MlineCommand {
|
||||||
|
|
@ -29,15 +31,23 @@ impl MlineCommand {
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
waiting_scale: false,
|
waiting_scale: false,
|
||||||
style_name: "Standard".into(),
|
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 {
|
Self {
|
||||||
points: vec![],
|
points: vec![],
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
waiting_scale: false,
|
waiting_scale: false,
|
||||||
style_name: style_name.into(),
|
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
|
// Close command
|
||||||
if (up == "C" || up == "CLOSE") && self.points.len() >= 3 {
|
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));
|
return Some(CmdResult::CommitAndExit(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +143,14 @@ impl CadCommand for MlineCommand {
|
||||||
if self.points.len() < 2 {
|
if self.points.len() < 2 {
|
||||||
return CmdResult::Cancel;
|
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)
|
CmdResult::CommitAndExit(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,18 +195,25 @@ impl CadCommand for MlineCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_mline(pts: &[DVec3], scale: f64, closed: bool, style_name: &str) -> EntityType {
|
fn build_mline(
|
||||||
let verts: Vec<Vector3> = pts
|
pts: &[DVec3],
|
||||||
.iter()
|
scale: f64,
|
||||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
closed: bool,
|
||||||
.collect();
|
style_name: &str,
|
||||||
let mut mline = if closed {
|
style_handle: Option<acadrust::Handle>,
|
||||||
MLine::closed_from_points(&verts)
|
style_element_count: usize,
|
||||||
} else {
|
) -> EntityType {
|
||||||
MLine::from_points(&verts)
|
let mut mline = MLine::new();
|
||||||
};
|
|
||||||
mline.scale_factor = scale;
|
mline.scale_factor = scale;
|
||||||
mline.style_name = style_name.to_string();
|
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)
|
EntityType::MLine(mline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -639,10 +639,33 @@ pub fn effective_annotation_scale_for(
|
||||||
fallback: f32,
|
fallback: f32,
|
||||||
scale_handle: Option<Handle>,
|
scale_handle: Option<Handle>,
|
||||||
) -> f32 {
|
) -> 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;
|
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
|
// Unlike MTEXT / DIMENSION contexts, an MLEADER context carries its
|
||||||
// already-scaled text height and overall scale factor. Keep the text
|
// already-scaled text height and overall scale factor. Keep the text
|
||||||
// height as stored; make `ml.scale_factor * anno_scale` resolve to the
|
// 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
|
/// Whether an object carries a per-object annotation context with at least one
|
||||||
/// per-scale representation — its extension dictionary holds an
|
/// per-scale representation — its extension dictionary holds an
|
||||||
/// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is
|
/// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is
|
||||||
|
|
@ -1454,6 +1486,7 @@ pub fn annotation_style_is_annotative(doc: &CadDocument, entity: &EntityType) ->
|
||||||
EntityType::MultiLeader(leader) => {
|
EntityType::MultiLeader(leader) => {
|
||||||
mleader_style_annotative(doc, leader.style_handle)
|
mleader_style_annotative(doc, leader.style_handle)
|
||||||
}
|
}
|
||||||
|
EntityType::Table(table) => table_style_annotative(doc, table.table_style_handle),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1473,6 +1506,8 @@ pub fn apply_mleader_style(
|
||||||
entity.dogleg_length = style.landing_distance;
|
entity.dogleg_length = style.landing_distance;
|
||||||
entity.arrowhead_handle = style.arrowhead_handle;
|
entity.arrowhead_handle = style.arrowhead_handle;
|
||||||
entity.arrowhead_size = style.arrowhead_size;
|
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_style_handle = style.text_style_handle;
|
||||||
entity.text_color = style.text_color;
|
entity.text_color = style.text_color;
|
||||||
entity.text_frame = style.text_frame;
|
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.block_content_scale = entity.block_scale;
|
||||||
entity.context.scale_factor = style.scale_factor;
|
entity.context.scale_factor = style.scale_factor;
|
||||||
entity.enable_annotation_scale = style.is_annotative;
|
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(
|
pub fn apply_mleader_style_to_object(
|
||||||
|
|
|
||||||
258
src/scene/creation_style.rs
Normal file
258
src/scene/creation_style.rs
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
pub mod annotative;
|
pub mod annotative;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
|
pub mod creation_style;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod pick;
|
pub mod pick;
|
||||||
pub mod pipeline;
|
pub mod pipeline;
|
||||||
|
|
@ -3800,6 +3801,28 @@ impl Scene {
|
||||||
self.scale_handle_ensuring("1:1")
|
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> {
|
pub fn set_annotation_scale_named(&mut self, name: &str) -> Option<Handle> {
|
||||||
let handle = self.scale_handle_ensuring(name)?;
|
let handle = self.scale_handle_ensuring(name)?;
|
||||||
let ObjectType::Scale(scale) = self.document.objects.get(&handle)? else {
|
let ObjectType::Scale(scale) = self.document.objects.get(&handle)? else {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue