fix: keep dimension text grips aligned during stretch
This commit is contained in:
parent
2ae8da543f
commit
2da520de80
2 changed files with 268 additions and 2 deletions
|
|
@ -1618,6 +1618,49 @@ impl OpenCADStudio {
|
|||
annotation_scale_handle,
|
||||
);
|
||||
let mut entity_grips = dispatch::grips(contextual.as_ref());
|
||||
// Dimension::grips() cannot see the document, so an automatic dimension
|
||||
// text grip cannot resolve its real DIMSTYLE/annotation-scaled position
|
||||
// there. Correct it here, where both the document and displayed annotation
|
||||
// scale are available.
|
||||
if let acadrust::EntityType::Dimension(dim) = contextual.as_ref() {
|
||||
if matches!(
|
||||
dim,
|
||||
acadrust::entities::Dimension::Linear(_)
|
||||
| acadrust::entities::Dimension::Aligned(_)
|
||||
) && !dim.base().text_user_positioned
|
||||
{
|
||||
let anno_scale = annotation_scale_handle
|
||||
.and_then(|handle| {
|
||||
match self.tabs[i].scene.document.objects.get(&handle) {
|
||||
Some(acadrust::objects::ObjectType::Scale(scale)) => Some(
|
||||
scale.inverse_factor()
|
||||
/ self.tabs[i].scene.annotation_scale_unit_factor(),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.unwrap_or(self.tabs[i].scene.annotation_scale as f64);
|
||||
|
||||
if let Some(position) =
|
||||
crate::entities::dimension::dimension_text_grip_position(
|
||||
dim,
|
||||
&self.tabs[i].scene.document,
|
||||
anno_scale,
|
||||
)
|
||||
{
|
||||
// The text grip is the final native grip of Linear/Aligned dims.
|
||||
// While the text is still automatic, make this a point/stretch grip
|
||||
// rather than a midpoint-translate grip. That makes the first drag use
|
||||
// the displayed automatic position as its absolute starting point instead
|
||||
// of translating the stale DWG text_middle_point.
|
||||
if let Some(text_grip) = entity_grips.last_mut() {
|
||||
text_grip.world =
|
||||
glam::DVec3::new(position.x, position.y, position.z);
|
||||
text_grip.is_midpoint = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entity_grips.extend(crate::scene::model::solid_history::primitive_grips(
|
||||
&self.tabs[i].scene.document,
|
||||
handle,
|
||||
|
|
|
|||
|
|
@ -853,6 +853,184 @@ fn apply_to_v3(target: &mut acadrust::types::Vector3, apply: &GripApply) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DimTextRelativePosition {
|
||||
along_fraction: f64,
|
||||
perpendicular_offset: f64,
|
||||
z_offset: f64,
|
||||
}
|
||||
|
||||
fn capture_dim_text_relative_position(dim: &Dimension) -> Option<DimTextRelativePosition> {
|
||||
let base = dim.base();
|
||||
|
||||
// Auto-positioned text is recomputed by the renderer from DIMSTYLE.
|
||||
// Only an explicitly moved text point needs to follow a grip deformation.
|
||||
if !base.text_user_positioned {
|
||||
return None;
|
||||
}
|
||||
|
||||
let text = base.text_middle_point;
|
||||
if text.x * text.x + text.y * text.y + text.z * text.z <= 1e-16 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (first, second, defpt, ax, ay) = match dim {
|
||||
Dimension::Linear(d) => (
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
d.rotation.cos(),
|
||||
d.rotation.sin(),
|
||||
),
|
||||
Dimension::Aligned(d) => {
|
||||
let dx = d.second_point.x - d.first_point.x;
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
if len <= 1e-12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
(
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
dx / len,
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let px = -ay;
|
||||
let py = ax;
|
||||
|
||||
let t1 = (first.x - defpt.x) * ax + (first.y - defpt.y) * ay;
|
||||
let t2 = (second.x - defpt.x) * ax + (second.y - defpt.y) * ay;
|
||||
let text_t = (text.x - defpt.x) * ax + (text.y - defpt.y) * ay;
|
||||
|
||||
let span = t2 - t1;
|
||||
let along_fraction = if span.abs() > 1e-12 {
|
||||
(text_t - t1) / span
|
||||
} else {
|
||||
0.5
|
||||
};
|
||||
|
||||
let along = t1 + span * along_fraction;
|
||||
let line_x = defpt.x + ax * along;
|
||||
let line_y = defpt.y + ay * along;
|
||||
|
||||
let perpendicular_offset =
|
||||
(text.x - line_x) * px + (text.y - line_y) * py;
|
||||
|
||||
Some(DimTextRelativePosition {
|
||||
along_fraction,
|
||||
perpendicular_offset,
|
||||
z_offset: text.z - defpt.z,
|
||||
})
|
||||
}
|
||||
|
||||
fn restore_dim_text_relative_position(
|
||||
dim: &mut Dimension,
|
||||
saved: DimTextRelativePosition,
|
||||
) {
|
||||
let (first, second, defpt, ax, ay) = match dim {
|
||||
Dimension::Linear(d) => (
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
d.rotation.cos(),
|
||||
d.rotation.sin(),
|
||||
),
|
||||
Dimension::Aligned(d) => {
|
||||
let dx = d.second_point.x - d.first_point.x;
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
if len <= 1e-12 {
|
||||
return;
|
||||
}
|
||||
|
||||
(
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
dx / len,
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let px = -ay;
|
||||
let py = ax;
|
||||
|
||||
let t1 = (first.x - defpt.x) * ax + (first.y - defpt.y) * ay;
|
||||
let t2 = (second.x - defpt.x) * ax + (second.y - defpt.y) * ay;
|
||||
|
||||
let along = t1 + (t2 - t1) * saved.along_fraction;
|
||||
|
||||
let base = dim.base_mut();
|
||||
base.text_middle_point = Vector3::new(
|
||||
defpt.x + ax * along + px * saved.perpendicular_offset,
|
||||
defpt.y + ay * along + py * saved.perpendicular_offset,
|
||||
defpt.z + saved.z_offset,
|
||||
);
|
||||
}
|
||||
|
||||
fn dimension_line_grip_position(dim: &Dimension) -> Option<DVec3> {
|
||||
let (first, second, defpt, ax, ay) = match dim {
|
||||
Dimension::Linear(d) => (
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
d.rotation.cos(),
|
||||
d.rotation.sin(),
|
||||
),
|
||||
Dimension::Aligned(d) => {
|
||||
let dx = d.second_point.x - d.first_point.x;
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if len <= 1e-12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
(
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
dx / len,
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let px = -ay;
|
||||
let py = ax;
|
||||
|
||||
// Project both extension origins onto the current dimension line.
|
||||
let off1 =
|
||||
(defpt.x - first.x) * px + (defpt.y - first.y) * py;
|
||||
let off2 =
|
||||
(defpt.x - second.x) * px + (defpt.y - second.y) * py;
|
||||
|
||||
let p1 = DVec3::new(
|
||||
first.x + px * off1,
|
||||
first.y + py * off1,
|
||||
defpt.z,
|
||||
);
|
||||
|
||||
let p2 = DVec3::new(
|
||||
second.x + px * off2,
|
||||
second.y + py * off2,
|
||||
defpt.z,
|
||||
);
|
||||
|
||||
Some((p1 + p2) * 0.5)
|
||||
}
|
||||
|
||||
impl Grippable for Dimension {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
// Auto-placed dimensions carry a zero text_middle_point sentinel; put
|
||||
|
|
@ -870,13 +1048,21 @@ impl Grippable for Dimension {
|
|||
Dimension::Linear(d) => vec![
|
||||
square_grip(0, dv3(&d.first_point)),
|
||||
center_grip(1, dv3(&d.second_point)),
|
||||
center_grip(2, dv3(&d.definition_point)),
|
||||
center_grip(
|
||||
2,
|
||||
dimension_line_grip_position(self)
|
||||
.unwrap_or_else(|| dv3(&d.definition_point)),
|
||||
),
|
||||
center_grip(3, text),
|
||||
],
|
||||
Dimension::Aligned(d) => vec![
|
||||
square_grip(0, dv3(&d.first_point)),
|
||||
center_grip(1, dv3(&d.second_point)),
|
||||
center_grip(2, dv3(&d.definition_point)),
|
||||
center_grip(
|
||||
2,
|
||||
dimension_line_grip_position(self)
|
||||
.unwrap_or_else(|| dv3(&d.definition_point)),
|
||||
),
|
||||
center_grip(3, text),
|
||||
],
|
||||
Dimension::Radius(d) => vec![
|
||||
|
|
@ -935,6 +1121,8 @@ impl Grippable for Dimension {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
// Last grip always moves the text.
|
||||
let text_grip = match self {
|
||||
|
|
@ -953,6 +1141,12 @@ impl Grippable for Dimension {
|
|||
return;
|
||||
}
|
||||
|
||||
// A manually positioned dimension text point is stored in the DWG as an
|
||||
// absolute coordinate. Capture its relation to the old dimension geometry
|
||||
// before moving a definition grip so that it can be reconstructed against
|
||||
// the new geometry afterwards.
|
||||
let relative_text = capture_dim_text_relative_position(self);
|
||||
|
||||
match self {
|
||||
Dimension::Linear(d) => match grip_id {
|
||||
0 => apply_to_v3(&mut d.first_point, &apply),
|
||||
|
|
@ -1013,6 +1207,11 @@ impl Grippable for Dimension {
|
|||
_ => {}
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(saved) = relative_text {
|
||||
restore_dim_text_relative_position(self, saved);
|
||||
}
|
||||
|
||||
self.base_mut().actual_measurement = self.measurement();
|
||||
}
|
||||
|
||||
|
|
@ -3800,6 +3999,30 @@ pub(crate) fn baked_dimension_text_entity(
|
|||
Some(ent)
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_text_grip_position(
|
||||
dim: &Dimension,
|
||||
document: &CadDocument,
|
||||
anno_scale: f64,
|
||||
) -> Option<Vector3> {
|
||||
// Once the user has moved the text, its saved point is authoritative.
|
||||
let base = dim.base();
|
||||
if base.text_user_positioned {
|
||||
let p = base.text_middle_point;
|
||||
if p.x * p.x + p.y * p.y + p.z * p.z > 1e-16 {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
|
||||
// For automatic text, use the same text-building path used by the
|
||||
// dimension picture so the grip follows the actual DIMSTYLE placement,
|
||||
// including annotation scaling and fit behaviour.
|
||||
match baked_dimension_text_entity(dim, document, anno_scale)? {
|
||||
EntityType::Text(text) => Some(text.insertion_point),
|
||||
EntityType::MText(text) => Some(text.insertion_point),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dimtad_tests {
|
||||
use super::text_on_dim_line;
|
||||
|
|
|
|||
Loading…
Reference in a new issue