forked from 1b-agent/opencadstudio-upstream
Merge pull request #1323 from ghost/diameter-grip-text-alignment
This commit is contained in:
commit
df53117541
3 files changed
+233
-10
No files matched your search
+100
-10
@@ -930,6 +930,23 @@ fn capture_dim_text_relative_position(dim: &Dimension) -> Option<DimTextRelative
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
Dimension::Diameter(d) => {
|
||||
let dx = d.definition_point.x - d.angle_vertex.x;
|
||||
let dy = d.definition_point.y - d.angle_vertex.y;
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if len <= 1e-12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
(
|
||||
d.angle_vertex,
|
||||
d.definition_point,
|
||||
d.angle_vertex,
|
||||
dx / len,
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
@@ -989,6 +1006,23 @@ fn restore_dim_text_relative_position(
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
Dimension::Diameter(d) => {
|
||||
let dx = d.definition_point.x - d.angle_vertex.x;
|
||||
let dy = d.definition_point.y - d.angle_vertex.y;
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if len <= 1e-12 {
|
||||
return;
|
||||
}
|
||||
|
||||
(
|
||||
d.angle_vertex,
|
||||
d.definition_point,
|
||||
d.angle_vertex,
|
||||
dx / len,
|
||||
dy / len,
|
||||
)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@@ -1211,6 +1245,26 @@ impl Grippable for Dimension {
|
||||
};
|
||||
if grip_id == text_grip {
|
||||
apply_to_v3(&mut self.base_mut().text_middle_point, &apply);
|
||||
|
||||
// A diametric dimension must remain collinear when its text/leader
|
||||
// grip is moved. Rotate both diameter endpoints around the fixed
|
||||
// center so the leader and diameter share the same radial axis.
|
||||
if let Dimension::Diameter(d) = self {
|
||||
let center = d.center();
|
||||
let radius = d.measurement() * 0.5;
|
||||
let offset = d.base.text_middle_point - center;
|
||||
|
||||
if offset.length_squared() > 1e-24 && radius > 1e-12 {
|
||||
let radial = offset.normalize() * radius;
|
||||
|
||||
// Keep the endpoint nearest the text on the text side.
|
||||
d.angle_vertex = center + radial;
|
||||
d.definition_point = center - radial;
|
||||
d.base.definition_point = d.definition_point;
|
||||
d.base.actual_measurement = d.measurement();
|
||||
}
|
||||
}
|
||||
|
||||
// Dragging the text grip pins it to a user-defined location, so it
|
||||
// no longer follows the style (DIMTAD). See #94.
|
||||
self.base_mut().text_user_positioned = true;
|
||||
@@ -4639,9 +4693,9 @@ fn dimension_geometry(
|
||||
&mut g,
|
||||
chord,
|
||||
far_chord,
|
||||
lv(d.base.text_middle_point),
|
||||
arrow1,
|
||||
arrow2,
|
||||
d.leader_length as f32,
|
||||
params,
|
||||
suppress,
|
||||
);
|
||||
@@ -5027,9 +5081,9 @@ fn append_diameter_dimension(
|
||||
g: &mut DimGeom,
|
||||
chord: Vec3,
|
||||
far_chord: Vec3,
|
||||
text_anchor: Vec3,
|
||||
arrow1: &ArrowKind,
|
||||
arrow2: &ArrowKind,
|
||||
leader_length: f32,
|
||||
params: DimLineParams,
|
||||
suppress: SuppressFlags,
|
||||
) {
|
||||
@@ -5038,9 +5092,14 @@ fn append_diameter_dimension(
|
||||
if diameter <= 1e-6 {
|
||||
return;
|
||||
}
|
||||
// Text projected outside the diameter requires inward-pointing arrowheads.
|
||||
let text_along = (params.text_position - chord).dot(axis);
|
||||
let text_outside = text_along < 0.0 || text_along > diameter;
|
||||
|
||||
let arrows_outside = if params.ticks || params.arrow_len <= 1e-6 {
|
||||
false
|
||||
} else if text_outside {
|
||||
true
|
||||
} else if diameter < 2.0 * params.arrow_len {
|
||||
true
|
||||
} else if diameter < params.text_width + 2.0 * params.arrow_len {
|
||||
@@ -5064,7 +5123,7 @@ fn append_diameter_dimension(
|
||||
.unwrap_or(line_length * 0.5);
|
||||
let split_point = first + axis * split;
|
||||
|
||||
let draw_inside_line = !arrows_outside || params.dimtofl;
|
||||
let draw_inside_line = text_outside || !arrows_outside || params.dimtofl;
|
||||
if draw_inside_line && !suppress.dim1 && split > 1e-6 {
|
||||
add_segment_with_text_break(&mut g.dim_lines, first, split_point, params.text_break);
|
||||
}
|
||||
@@ -5089,18 +5148,25 @@ fn append_diameter_dimension(
|
||||
append_arrow(g, far_chord, -axis, arrow2);
|
||||
}
|
||||
|
||||
let text_along = (params.text_position - chord).dot(axis);
|
||||
if params.text_movement == 0 {
|
||||
let (tip, suppressed) = if params.text_position.distance_squared(chord)
|
||||
let (tip, direction, suppressed) = if params.text_position.distance_squared(chord)
|
||||
<= params.text_position.distance_squared(far_chord)
|
||||
{
|
||||
(chord, suppress.dim1)
|
||||
(chord, -axis, suppress.dim1)
|
||||
} else {
|
||||
(far_chord, suppress.dim2)
|
||||
(far_chord, axis, suppress.dim2)
|
||||
};
|
||||
if !suppressed && leader_length.abs() > 1e-6 {
|
||||
let direction = normalized_or(params.text_position - tip, axis);
|
||||
add_segment(&mut g.dim_lines, tip, tip + direction * leader_length.abs());
|
||||
if text_outside && !suppressed {
|
||||
let leader_length = (text_anchor - tip).dot(direction);
|
||||
|
||||
if leader_length > 1e-6 {
|
||||
let text_extension = params.text_width * 0.5;
|
||||
add_segment(
|
||||
&mut g.dim_lines,
|
||||
tip,
|
||||
tip + direction * (leader_length + text_extension),
|
||||
);
|
||||
}
|
||||
} else if text_along < 0.0 && !suppress.dim1 {
|
||||
add_segment(&mut g.dim_lines, chord, params.text_position);
|
||||
} else if text_along > diameter && !suppress.dim2 {
|
||||
@@ -7115,6 +7181,30 @@ fn dimension_text_pos_f64(
|
||||
p.x * p.x + p.y * p.y + p.z * p.z > 1e-16
|
||||
};
|
||||
if use_saved {
|
||||
if let Dimension::Diameter(d) = dim {
|
||||
let dx = d.definition_point.x - d.angle_vertex.x;
|
||||
let dy = d.definition_point.y - d.angle_vertex.y;
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-12);
|
||||
|
||||
// Match the readable orientation used by text_on_dim_line().
|
||||
let (mut nx, mut ny) = (dx / len, dy / len);
|
||||
if nx < 0.0 || (nx == 0.0 && ny < 0.0) {
|
||||
nx = -nx;
|
||||
ny = -ny;
|
||||
}
|
||||
|
||||
let px = -ny;
|
||||
let py = nx;
|
||||
|
||||
let perp_sign = if dimtad == 4 { -1.0 } else { 1.0 };
|
||||
|
||||
return Vector3::new(
|
||||
base.text_middle_point.x + px * perp_off * perp_sign,
|
||||
base.text_middle_point.y + py * perp_off * perp_sign,
|
||||
base.text_middle_point.z,
|
||||
);
|
||||
}
|
||||
|
||||
return base.text_middle_point;
|
||||
}
|
||||
|
||||
|
||||
@@ -2157,6 +2157,138 @@ impl Scene {
|
||||
}
|
||||
refreshed
|
||||
}
|
||||
pub(crate) fn sync_diameter_association_angle(&mut self, dimension: Handle) {
|
||||
let chord = match self.document.get_entity(dimension) {
|
||||
Some(EntityType::Dimension(Dimension::Diameter(diameter))) => {
|
||||
diameter.angle_vertex
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let association_handle = self.document.objects.iter().find_map(|(handle, object)| {
|
||||
let ObjectType::Associative(object) = object else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let AssociativeData::DimensionAssociation(association) = &object.data else {
|
||||
return None;
|
||||
};
|
||||
|
||||
(association.dimension == dimension).then_some(*handle)
|
||||
});
|
||||
|
||||
let Some(association_handle) = association_handle else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Resolve the radial source while the association is borrowed immutably.
|
||||
let angle = {
|
||||
let Some(ObjectType::Associative(object)) =
|
||||
self.document.objects.get(&association_handle)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let AssociativeData::DimensionAssociation(association) = &object.data else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(reference) = association.references[0].first() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(walked) = chain::walk_chain(&self.document, &reference.xrefs) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(entity) = self.document.get_entity(walked.entity) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(radial) = radial_source_for_marker(entity, reference.main_gs_marker) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(radial) = chain_map(self, &walked).map_radial(radial) else {
|
||||
return;
|
||||
};
|
||||
|
||||
radial.angle_at(dpoint(chord))
|
||||
};
|
||||
|
||||
if self.is_recording_undo() {
|
||||
let before = self.document.objects.get(&association_handle).cloned();
|
||||
self.record_undo_object_before(association_handle, before);
|
||||
}
|
||||
|
||||
let Some(ObjectType::Associative(object)) =
|
||||
self.document.objects.get_mut(&association_handle)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let AssociativeData::DimensionAssociation(association) = &mut object.data else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(reference) = association.references[0].first_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
reference.osnap_distance = angle;
|
||||
reference.osnap_point = chord;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use acadrust::entities::{Circle, DimensionDiameter};
|
||||
|
||||
#[test]
|
||||
fn diameter_angle_sync_records_the_association_for_undo() {
|
||||
let mut scene = Scene::new();
|
||||
let circle = scene.add_entity(EntityType::Circle(Circle::from_center_radius(
|
||||
Vector3::ZERO,
|
||||
5.0,
|
||||
)));
|
||||
let dimension = scene.add_entity(EntityType::Dimension(Dimension::Diameter(
|
||||
DimensionDiameter::new(
|
||||
Vector3::new(5.0, 0.0, 0.0),
|
||||
Vector3::new(-5.0, 0.0, 0.0),
|
||||
),
|
||||
)));
|
||||
scene.attach_dimension_association(dimension, vec![Some(circle)]);
|
||||
let association = scene
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
ObjectType::Associative(object)
|
||||
if matches!(
|
||||
object.data,
|
||||
AssociativeData::DimensionAssociation(ref association)
|
||||
if association.dimension == dimension
|
||||
) => Some(*handle),
|
||||
_ => None,
|
||||
})
|
||||
.expect("diameter association");
|
||||
|
||||
scene.begin_undo_recording();
|
||||
let Some(EntityType::Dimension(Dimension::Diameter(diameter))) =
|
||||
scene.document.get_entity_mut(dimension)
|
||||
else {
|
||||
panic!("diameter dimension");
|
||||
};
|
||||
diameter.angle_vertex = Vector3::new(0.0, 5.0, 0.0);
|
||||
scene.sync_diameter_association_angle(dimension);
|
||||
|
||||
let recording = scene.take_undo_recording().expect("undo recording");
|
||||
let (_, objects, _, _) = recording.into_recorded_images();
|
||||
assert!(objects
|
||||
.iter()
|
||||
.any(|(handle, before)| *handle == association && before.is_some()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Average displacement of the dimension's resolved definition points.
|
||||
|
||||
@@ -1399,6 +1399,7 @@ impl Scene {
|
||||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||
view::dispatch::apply_grip(entity, grip_id, apply);
|
||||
}
|
||||
self.sync_diameter_association_angle(handle);
|
||||
if let Some((annotation_handle, old_landing)) = leader_landing_before {
|
||||
let new_landing = self.document.get_entity(handle).and_then(|entity| {
|
||||
let EntityType::Leader(leader) = entity else {
|
||||
|
||||
Reference in new issue
Block a user