Complete angular dimension creation and associations
This commit is contained in:
parent
20c7a2df58
commit
e93815d0c5
5 changed files with 749 additions and 153 deletions
|
|
@ -1229,17 +1229,24 @@ impl OpenCADStudio {
|
|||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::CommitAssociativeDimension { entity, source } => {
|
||||
let label = self.history_label_from_active_cmd(i, "DIMLINEAR");
|
||||
CmdResult::CommitAssociativeDimension { entity, sources } => {
|
||||
let label = self.history_label_from_active_cmd(i, "DIMENSION");
|
||||
let pending = self.begin_undo(i, label, 1, false);
|
||||
if let Some(handle) = self.commit_entity_handle(entity) {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.attach_linear_dimension_association(handle, [Some(source), Some(source)]);
|
||||
self.tabs[i].scene.bump_entities(&[
|
||||
(handle, crate::scene::ChangeKind::Modified),
|
||||
(source, crate::scene::ChangeKind::Modified),
|
||||
]);
|
||||
.attach_dimension_association(
|
||||
handle,
|
||||
sources.iter().copied().map(Some).collect(),
|
||||
);
|
||||
let mut changed = vec![(handle, crate::scene::ChangeKind::Modified)];
|
||||
changed.extend(
|
||||
sources
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|source| (source, crate::scene::ChangeKind::Modified)),
|
||||
);
|
||||
self.tabs[i].scene.bump_entities(&changed);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
|
|
|
|||
|
|
@ -1212,10 +1212,10 @@ pub enum CmdResult {
|
|||
CommitEntitiesAndExit(Vec<EntityType>),
|
||||
/// Commit an acadrust entity to the document and end the command.
|
||||
CommitAndExit(EntityType),
|
||||
/// Commit an object-selected linear dimension and retain its source link.
|
||||
/// Commit an object-selected dimension and retain its source links.
|
||||
CommitAssociativeDimension {
|
||||
entity: EntityType,
|
||||
source: Handle,
|
||||
sources: Vec<Handle>,
|
||||
},
|
||||
/// Commit a Model-tab 3D solid: the acadrust entity (for selection /
|
||||
/// persistence) plus its B-rep (cached for boolean ops + shaded
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use acadrust::entities::{Dimension, DimensionAngular3Pt};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::entities::{Dimension, DimensionAngular2Ln, DimensionAngular3Pt};
|
||||
use acadrust::types::{Handle, Vector3};
|
||||
use acadrust::EntityType;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use glam::{DVec3, Vec3};
|
||||
use crate::t;
|
||||
use glam::DVec3;
|
||||
|
||||
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/dim_angular.svg"));
|
||||
|
||||
|
|
@ -22,20 +22,55 @@ pub fn tool() -> ToolDef {
|
|||
enum Step {
|
||||
Vertex,
|
||||
FirstRay(DVec3),
|
||||
SecondRay {
|
||||
SecondRay { vertex: DVec3, first: DVec3 },
|
||||
CircleSecondRay {
|
||||
vertex: DVec3,
|
||||
first: DVec3,
|
||||
radius: f64,
|
||||
source: Handle,
|
||||
},
|
||||
ArcPoint {
|
||||
SecondLine {
|
||||
first_start: DVec3,
|
||||
first_end: DVec3,
|
||||
first_source: Handle,
|
||||
},
|
||||
ArcPoint3 {
|
||||
vertex: DVec3,
|
||||
first: DVec3,
|
||||
second: DVec3,
|
||||
},
|
||||
ArcPoint2 {
|
||||
first_start: DVec3,
|
||||
first_end: DVec3,
|
||||
second_start: DVec3,
|
||||
second_end: DVec3,
|
||||
},
|
||||
}
|
||||
|
||||
enum PickedCurve {
|
||||
Line(DVec3, DVec3),
|
||||
Arc {
|
||||
center: DVec3,
|
||||
first: DVec3,
|
||||
second: DVec3,
|
||||
},
|
||||
Circle {
|
||||
center: DVec3,
|
||||
radius: f64,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct AngularDimensionCommand {
|
||||
step: Step,
|
||||
plane: WorkingPlane,
|
||||
selecting_object: bool,
|
||||
picked_entity: Option<EntityType>,
|
||||
source_handles: Vec<Handle>,
|
||||
text_override: Option<String>,
|
||||
awaiting_text: bool,
|
||||
mtext_override: bool,
|
||||
text_angle: Option<f64>,
|
||||
awaiting_angle: bool,
|
||||
}
|
||||
|
||||
impl AngularDimensionCommand {
|
||||
|
|
@ -43,8 +78,92 @@ impl AngularDimensionCommand {
|
|||
Self {
|
||||
step: Step::Vertex,
|
||||
plane: WorkingPlane::default(),
|
||||
selecting_object: false,
|
||||
picked_entity: None,
|
||||
source_handles: Vec::new(),
|
||||
text_override: None,
|
||||
awaiting_text: false,
|
||||
mtext_override: false,
|
||||
text_angle: None,
|
||||
awaiting_angle: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_three_point(
|
||||
&self,
|
||||
vertex: DVec3,
|
||||
first: DVec3,
|
||||
second: DVec3,
|
||||
arc_point: DVec3,
|
||||
) -> CmdResult {
|
||||
let vertex = self.plane.to_local(vertex);
|
||||
let first = self.plane.to_local(first);
|
||||
let second = self.plane.to_local(second);
|
||||
let arc_point = self.plane.to_local(arc_point);
|
||||
let mut dim = DimensionAngular3Pt::new(v3(vertex), v3(first), v3(second));
|
||||
dim.definition_point = v3(arc_point);
|
||||
dim.base.definition_point = dim.definition_point;
|
||||
dim.base.text_middle_point = dim.definition_point;
|
||||
dim.base.insertion_point = dim.definition_point;
|
||||
dim.base.actual_measurement = dim.measurement_degrees();
|
||||
crate::entities::dimension::set_dimension_text_override(
|
||||
&mut dim.base,
|
||||
self.text_override.clone(),
|
||||
);
|
||||
if let Some(angle) = self.text_angle {
|
||||
dim.base.text_rotation = angle;
|
||||
}
|
||||
self.commit(Dimension::Angular3Pt(dim))
|
||||
}
|
||||
|
||||
fn finish_two_line(
|
||||
&self,
|
||||
first_start: DVec3,
|
||||
first_end: DVec3,
|
||||
second_start: DVec3,
|
||||
second_end: DVec3,
|
||||
arc_point: DVec3,
|
||||
) -> CmdResult {
|
||||
let first_start = self.plane.to_local(first_start);
|
||||
let first_end = self.plane.to_local(first_end);
|
||||
let second_start = self.plane.to_local(second_start);
|
||||
let second_end = self.plane.to_local(second_end);
|
||||
let arc_point = self.plane.to_local(arc_point);
|
||||
let mut dim = DimensionAngular2Ln::default();
|
||||
dim.first_point = v3(first_start);
|
||||
dim.second_point = v3(first_end);
|
||||
dim.angle_vertex = v3(second_start);
|
||||
dim.definition_point = v3(second_end);
|
||||
dim.dimension_arc = v3(arc_point);
|
||||
dim.base.definition_point = dim.dimension_arc;
|
||||
dim.base.text_middle_point = dim.dimension_arc;
|
||||
dim.base.insertion_point = dim.dimension_arc;
|
||||
dim.base.actual_measurement = dim.measurement_degrees();
|
||||
crate::entities::dimension::set_dimension_text_override(
|
||||
&mut dim.base,
|
||||
self.text_override.clone(),
|
||||
);
|
||||
if let Some(angle) = self.text_angle {
|
||||
dim.base.text_rotation = angle;
|
||||
}
|
||||
self.commit(Dimension::Angular2Ln(dim))
|
||||
}
|
||||
|
||||
fn commit(&self, dimension: Dimension) -> CmdResult {
|
||||
let entity = self.plane.place_entity(EntityType::Dimension(dimension));
|
||||
if self.source_handles.is_empty() {
|
||||
CmdResult::CommitAndExit(entity)
|
||||
} else {
|
||||
CmdResult::CommitAssociativeDimension {
|
||||
entity,
|
||||
sources: self.source_handles.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn placement_step(&self) -> bool {
|
||||
matches!(self.step, Step::ArcPoint3 { .. } | Step::ArcPoint2 { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for AngularDimensionCommand {
|
||||
|
|
@ -57,59 +176,102 @@ impl CadCommand for AngularDimensionCommand {
|
|||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if self.awaiting_text {
|
||||
return if self.mtext_override {
|
||||
t!("DIMANGULAR Enter formatted dimension text (blank = measured value):")
|
||||
.into_owned()
|
||||
} else {
|
||||
t!("DIMANGULAR Enter dimension text (blank = measured value):").into_owned()
|
||||
};
|
||||
}
|
||||
if self.awaiting_angle {
|
||||
return t!("DIMANGULAR Specify text angle (degrees):").into_owned();
|
||||
}
|
||||
if self.selecting_object {
|
||||
return match self.step {
|
||||
Step::SecondLine { .. } => t!("DIMANGULAR Select second line:").into_owned(),
|
||||
_ => t!("DIMANGULAR Select arc, circle, line, or polyline arc:").into_owned(),
|
||||
};
|
||||
}
|
||||
match self.step {
|
||||
Step::Vertex => t!("DIMANGULAR Specify angle vertex:").into_owned(),
|
||||
Step::Vertex => t!(
|
||||
"DIMANGULAR Specify angle vertex or press Enter to select an object:"
|
||||
)
|
||||
.into_owned(),
|
||||
Step::FirstRay(_) => {
|
||||
t!("DIMANGULAR Specify first extension line point:").into_owned()
|
||||
}
|
||||
Step::SecondRay { .. } => {
|
||||
Step::SecondRay { .. } | Step::CircleSecondRay { .. } => {
|
||||
t!("DIMANGULAR Specify second extension line point:").into_owned()
|
||||
}
|
||||
Step::ArcPoint { .. } => t!("DIMANGULAR Specify dimension arc location:").into_owned(),
|
||||
Step::SecondLine { .. } => t!("DIMANGULAR Select second line:").into_owned(),
|
||||
Step::ArcPoint3 { .. } | Step::ArcPoint2 { .. } => t!(
|
||||
"DIMANGULAR Specify dimension arc location [Mtext/Text/Angle/Quadrant]:"
|
||||
)
|
||||
.into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::Vertex => {
|
||||
self.step = Step::FirstRay(pt);
|
||||
self.step = Step::FirstRay(point);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::FirstRay(vertex) => {
|
||||
self.step = Step::SecondRay { vertex, first: pt };
|
||||
if point.distance_squared(vertex) <= 1.0e-24 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
self.step = Step::SecondRay { vertex, first: point };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::SecondRay { vertex, first } => {
|
||||
self.step = Step::ArcPoint {
|
||||
vertex,
|
||||
first,
|
||||
second: pt,
|
||||
};
|
||||
if point.distance_squared(vertex) <= 1.0e-24 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
self.step = Step::ArcPoint3 { vertex, first, second: point };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::ArcPoint {
|
||||
vertex,
|
||||
first,
|
||||
second,
|
||||
} => {
|
||||
let vertex = self.plane.to_local(vertex);
|
||||
let first = self.plane.to_local(first);
|
||||
let second = self.plane.to_local(second);
|
||||
let pt = self.plane.to_local(pt);
|
||||
let mut dim = DimensionAngular3Pt::new(v3(vertex), v3(first), v3(second));
|
||||
dim.definition_point = v3(pt);
|
||||
dim.base.definition_point = v3(pt);
|
||||
dim.base.text_middle_point = v3(pt);
|
||||
dim.base.insertion_point = v3(pt);
|
||||
dim.base.actual_measurement = dim.measurement_degrees();
|
||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::Dimension(
|
||||
Dimension::Angular3Pt(dim),
|
||||
)))
|
||||
Step::CircleSecondRay { vertex, first, radius, source } => {
|
||||
let Some(second) = project_to_radius(vertex, point, radius) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
self.source_handles = vec![source, source, source];
|
||||
self.step = Step::ArcPoint3 { vertex, first, second };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::SecondLine { .. } => CmdResult::NeedPoint,
|
||||
Step::ArcPoint3 { vertex, first, second } => {
|
||||
self.finish_three_point(vertex, first, second, point)
|
||||
}
|
||||
Step::ArcPoint2 {
|
||||
first_start,
|
||||
first_end,
|
||||
second_start,
|
||||
second_end,
|
||||
} => self.finish_two_line(
|
||||
first_start,
|
||||
first_end,
|
||||
second_start,
|
||||
second_end,
|
||||
point,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.awaiting_text {
|
||||
self.awaiting_text = false;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
if self.awaiting_angle {
|
||||
self.awaiting_angle = false;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
if matches!(self.step, Step::Vertex) {
|
||||
self.selecting_object = true;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
|
|
@ -117,49 +279,392 @@ impl CadCommand for AngularDimensionCommand {
|
|||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
match self.step {
|
||||
Step::Vertex => None,
|
||||
Step::FirstRay(vertex) => Some(preview_wire(vec![vertex.as_vec3(), pt])),
|
||||
Step::SecondRay { vertex, first } => Some(preview_wire(vec![
|
||||
vertex.as_vec3(),
|
||||
first.as_vec3(),
|
||||
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
|
||||
vertex.as_vec3(),
|
||||
pt,
|
||||
])),
|
||||
Step::ArcPoint {
|
||||
vertex,
|
||||
first,
|
||||
second,
|
||||
} => {
|
||||
let points = angular_preview(
|
||||
self.plane.to_local(vertex).as_vec3(),
|
||||
self.plane.to_local(first).as_vec3(),
|
||||
self.plane.to_local(second).as_vec3(),
|
||||
self.plane.to_local(pt.as_dvec3()).as_vec3(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|point| {
|
||||
if point.x.is_nan() {
|
||||
point
|
||||
} else {
|
||||
self.plane.to_world(point.as_dvec3()).as_vec3()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(preview_wire(points))
|
||||
}
|
||||
fn wants_text_input(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn wants_text_with_spaces(&self) -> bool {
|
||||
self.awaiting_text
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
self.placement_step() && !self.awaiting_text && !self.awaiting_angle
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if self.awaiting_text {
|
||||
let value = text.trim();
|
||||
self.text_override = if value.is_empty() || value == "<>" {
|
||||
None
|
||||
} else {
|
||||
Some(value.to_string())
|
||||
};
|
||||
self.awaiting_text = false;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if self.awaiting_angle {
|
||||
self.text_angle = if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
crate::entities::common::parse_typed_angle(text.trim())
|
||||
};
|
||||
self.awaiting_angle = false;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if !self.placement_step() {
|
||||
return None;
|
||||
}
|
||||
match text.trim().to_ascii_uppercase().as_str() {
|
||||
"T" | "TEXT" => {
|
||||
self.mtext_override = false;
|
||||
self.awaiting_text = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"M" | "MTEXT" => {
|
||||
self.mtext_override = true;
|
||||
self.awaiting_text = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"A" | "ANGLE" => {
|
||||
self.awaiting_angle = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"Q" | "QUADRANT" => Some(CmdResult::NeedPoint),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
self.selecting_object
|
||||
}
|
||||
|
||||
fn entity_pick_highlights_hover(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn inject_before_entity_pick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn inject_picked_entity(&mut self, entity: EntityType) {
|
||||
self.picked_entity = Some(entity);
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, point: DVec3) -> CmdResult {
|
||||
if handle.is_null() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let Some(curve) = picked_curve(&entity, point) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
|
||||
match (&self.step, curve) {
|
||||
(
|
||||
Step::SecondLine { first_start, first_end, first_source },
|
||||
PickedCurve::Line(second_start, second_end),
|
||||
) if *first_source != handle => {
|
||||
self.source_handles = vec![*first_source, *first_source, handle, handle];
|
||||
self.selecting_object = false;
|
||||
self.step = Step::ArcPoint2 {
|
||||
first_start: *first_start,
|
||||
first_end: *first_end,
|
||||
second_start,
|
||||
second_end,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
(Step::Vertex, PickedCurve::Line(first_start, first_end)) => {
|
||||
self.step = Step::SecondLine {
|
||||
first_start,
|
||||
first_end,
|
||||
first_source: handle,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
(Step::Vertex, PickedCurve::Arc { center, first, second }) => {
|
||||
self.source_handles = vec![handle, handle, handle];
|
||||
self.selecting_object = false;
|
||||
self.step = Step::ArcPoint3 { vertex: center, first, second };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
(Step::Vertex, PickedCurve::Circle { center, radius }) => {
|
||||
let Some(first) = project_to_radius(center, point, radius) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
self.selecting_object = false;
|
||||
self.step = Step::CircleSecondRay {
|
||||
vertex: center,
|
||||
first,
|
||||
radius,
|
||||
source: handle,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
_ => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, point: DVec3) -> Option<WireModel> {
|
||||
let points = match self.step {
|
||||
Step::Vertex | Step::SecondLine { .. } => return None,
|
||||
Step::FirstRay(vertex) => vec![vertex, point],
|
||||
Step::SecondRay { vertex, first } => vec![vertex, first, nan(), vertex, point],
|
||||
Step::CircleSecondRay { vertex, first, radius, .. } => {
|
||||
let second = project_to_radius(vertex, point, radius).unwrap_or(point);
|
||||
vec![vertex, first, nan(), vertex, second]
|
||||
}
|
||||
Step::ArcPoint3 { vertex, first, second } => {
|
||||
angular_preview(vertex, first, second, point)
|
||||
}
|
||||
Step::ArcPoint2 {
|
||||
first_start,
|
||||
first_end,
|
||||
second_start,
|
||||
second_end,
|
||||
} => two_line_preview(
|
||||
first_start,
|
||||
first_end,
|
||||
second_start,
|
||||
second_end,
|
||||
point,
|
||||
),
|
||||
};
|
||||
Some(preview_wire(points))
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(pt: DVec3) -> Vector3 {
|
||||
Vector3::new(pt.x, pt.y, pt.z)
|
||||
fn picked_curve(entity: &EntityType, click: DVec3) -> Option<PickedCurve> {
|
||||
let point = |value: Vector3| DVec3::new(value.x, value.y, value.z);
|
||||
match entity {
|
||||
EntityType::Line(line) => Some(PickedCurve::Line(point(line.start), point(line.end))),
|
||||
EntityType::Arc(arc) => Some(PickedCurve::Arc {
|
||||
center: point(arc.center_wcs()),
|
||||
first: point(arc.start_point_wcs()),
|
||||
second: point(arc.end_point_wcs()),
|
||||
}),
|
||||
EntityType::Circle(circle) => Some(PickedCurve::Circle {
|
||||
center: point(circle.center_wcs()),
|
||||
radius: circle.radius,
|
||||
}),
|
||||
EntityType::LwPolyline(polyline) => {
|
||||
let click = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(click.x, click.y, click.z),
|
||||
(polyline.normal.x, polyline.normal.y, polyline.normal.z),
|
||||
);
|
||||
let segment = nearest_segment_index(
|
||||
polyline.vertices.iter().map(|vertex| [vertex.location.x, vertex.location.y]).collect(),
|
||||
polyline.is_closed,
|
||||
[click.0, click.1],
|
||||
)?;
|
||||
let first = polyline.vertices[segment];
|
||||
let second = polyline.vertices[(segment + 1) % polyline.vertices.len()];
|
||||
polyline_segment(
|
||||
[first.location.x, first.location.y],
|
||||
[second.location.x, second.location.y],
|
||||
first.bulge,
|
||||
polyline.elevation,
|
||||
polyline.normal,
|
||||
)
|
||||
}
|
||||
EntityType::Polyline2D(polyline) => {
|
||||
let vertices = crate::entities::polyline::drawn_vertices2d(polyline)
|
||||
.unwrap_or_else(|| polyline.vertices.clone());
|
||||
let click = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(click.x, click.y, click.z),
|
||||
(polyline.normal.x, polyline.normal.y, polyline.normal.z),
|
||||
);
|
||||
let segment = nearest_segment_index(
|
||||
vertices.iter().map(|vertex| [vertex.location.x, vertex.location.y]).collect(),
|
||||
polyline.is_closed(),
|
||||
[click.0, click.1],
|
||||
)?;
|
||||
let first = &vertices[segment];
|
||||
let second = &vertices[(segment + 1) % vertices.len()];
|
||||
polyline_segment(
|
||||
[first.location.x, first.location.y],
|
||||
[second.location.x, second.location.y],
|
||||
first.bulge,
|
||||
polyline.elevation,
|
||||
polyline.normal,
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
fn nearest_segment_index(points: Vec<[f64; 2]>, closed: bool, click: [f64; 2]) -> Option<usize> {
|
||||
if points.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let count = if closed { points.len() } else { points.len() - 1 };
|
||||
(0..count).min_by(|first, second| {
|
||||
segment_distance_squared(points[*first], points[(*first + 1) % points.len()], click)
|
||||
.total_cmp(&segment_distance_squared(
|
||||
points[*second],
|
||||
points[(*second + 1) % points.len()],
|
||||
click,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn segment_distance_squared(first: [f64; 2], second: [f64; 2], point: [f64; 2]) -> f64 {
|
||||
let dx = second[0] - first[0];
|
||||
let dy = second[1] - first[1];
|
||||
let length_squared = dx * dx + dy * dy;
|
||||
if length_squared <= 1.0e-24 {
|
||||
return (point[0] - first[0]).powi(2) + (point[1] - first[1]).powi(2);
|
||||
}
|
||||
let t = (((point[0] - first[0]) * dx + (point[1] - first[1]) * dy) / length_squared)
|
||||
.clamp(0.0, 1.0);
|
||||
let x = first[0] + dx * t;
|
||||
let y = first[1] + dy * t;
|
||||
(point[0] - x).powi(2) + (point[1] - y).powi(2)
|
||||
}
|
||||
|
||||
fn polyline_segment(
|
||||
first: [f64; 2],
|
||||
second: [f64; 2],
|
||||
bulge: f64,
|
||||
elevation: f64,
|
||||
normal: Vector3,
|
||||
) -> Option<PickedCurve> {
|
||||
let to_world = |point: [f64; 2]| {
|
||||
let value = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(point[0], point[1], elevation),
|
||||
(normal.x, normal.y, normal.z),
|
||||
);
|
||||
DVec3::new(value.0, value.1, value.2)
|
||||
};
|
||||
let first_world = to_world(first);
|
||||
let second_world = to_world(second);
|
||||
if bulge.abs() <= 1.0e-12 {
|
||||
return Some(PickedCurve::Line(first_world, second_world));
|
||||
}
|
||||
let dx = second[0] - first[0];
|
||||
let dy = second[1] - first[1];
|
||||
let chord = (dx * dx + dy * dy).sqrt();
|
||||
if chord <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
let midpoint = [(first[0] + second[0]) * 0.5, (first[1] + second[1]) * 0.5];
|
||||
let center_offset = chord * (1.0 - bulge * bulge) / (4.0 * bulge);
|
||||
let center = [
|
||||
midpoint[0] - dy / chord * center_offset,
|
||||
midpoint[1] + dx / chord * center_offset,
|
||||
];
|
||||
Some(PickedCurve::Arc {
|
||||
center: to_world(center),
|
||||
first: first_world,
|
||||
second: second_world,
|
||||
})
|
||||
}
|
||||
|
||||
fn project_to_radius(center: DVec3, point: DVec3, radius: f64) -> Option<DVec3> {
|
||||
let direction = (point - center).normalize_or_zero();
|
||||
(direction.length_squared() > 0.0).then_some(center + direction * radius)
|
||||
}
|
||||
|
||||
fn two_line_frame(
|
||||
first_start: DVec3,
|
||||
first_end: DVec3,
|
||||
second_start: DVec3,
|
||||
second_end: DVec3,
|
||||
arc_point: DVec3,
|
||||
) -> Option<(DVec3, f64, f64)> {
|
||||
let first_direction = first_end - first_start;
|
||||
let second_direction = second_end - second_start;
|
||||
let denominator = first_direction.x * second_direction.y - first_direction.y * second_direction.x;
|
||||
if denominator.abs()
|
||||
<= 1.0e-12 * first_direction.length().max(second_direction.length()).max(1.0)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let offset = second_start - first_start;
|
||||
let t = (offset.x * second_direction.y - offset.y * second_direction.x) / denominator;
|
||||
let vertex = first_start + first_direction * t;
|
||||
let target = (arc_point.y - vertex.y).atan2(arc_point.x - vertex.x);
|
||||
let mut best: Option<(f64, f64, f64)> = None;
|
||||
for first_sign in [1.0, -1.0] {
|
||||
for second_sign in [1.0, -1.0] {
|
||||
let first = first_direction * first_sign;
|
||||
let second = second_direction * second_sign;
|
||||
for (start, end) in [
|
||||
(first.y.atan2(first.x), second.y.atan2(second.x)),
|
||||
(second.y.atan2(second.x), first.y.atan2(first.x)),
|
||||
] {
|
||||
let sweep = (end - start).rem_euclid(std::f64::consts::TAU);
|
||||
if sweep <= 1.0e-9 || sweep > std::f64::consts::PI {
|
||||
continue;
|
||||
}
|
||||
let selected = (target - start).rem_euclid(std::f64::consts::TAU);
|
||||
if selected <= sweep + 1.0e-9 && best.is_none_or(|known| sweep < known.2) {
|
||||
best = Some((start, start + sweep, sweep));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
best.map(|(start, end, _)| (vertex, start, end))
|
||||
}
|
||||
|
||||
fn angular_preview(vertex: DVec3, first: DVec3, second: DVec3, arc_point: DVec3) -> Vec<DVec3> {
|
||||
let Some((_, start, end)) = two_line_frame(vertex, first, vertex, second, arc_point) else {
|
||||
return vec![vertex, first, nan(), vertex, second];
|
||||
};
|
||||
angular_preview_with_frame(vertex, first, second, arc_point, start, end)
|
||||
}
|
||||
|
||||
fn two_line_preview(
|
||||
first_start: DVec3,
|
||||
first_end: DVec3,
|
||||
second_start: DVec3,
|
||||
second_end: DVec3,
|
||||
arc_point: DVec3,
|
||||
) -> Vec<DVec3> {
|
||||
let Some((vertex, start, end)) = two_line_frame(
|
||||
first_start,
|
||||
first_end,
|
||||
second_start,
|
||||
second_end,
|
||||
arc_point,
|
||||
) else {
|
||||
return vec![first_start, first_end, nan(), second_start, second_end];
|
||||
};
|
||||
angular_preview_with_frame(vertex, first_start, second_start, arc_point, start, end)
|
||||
}
|
||||
|
||||
fn angular_preview_with_frame(
|
||||
vertex: DVec3,
|
||||
first: DVec3,
|
||||
second: DVec3,
|
||||
arc_point: DVec3,
|
||||
start: f64,
|
||||
end: f64,
|
||||
) -> Vec<DVec3> {
|
||||
let radius = vertex.distance(arc_point);
|
||||
let first_end = vertex + DVec3::new(start.cos(), start.sin(), 0.0) * radius;
|
||||
let second_end = vertex + DVec3::new(end.cos(), end.sin(), 0.0) * radius;
|
||||
let mut points = vec![first, first_end, nan(), second, second_end, nan()];
|
||||
let sweep = end - start;
|
||||
let steps = ((sweep.abs() / std::f64::consts::PI * 90.0).ceil() as usize).clamp(8, 720);
|
||||
for index in 0..=steps {
|
||||
let angle = start + sweep * index as f64 / steps as f64;
|
||||
points.push(vertex + DVec3::new(angle.cos() * radius, angle.sin() * radius, 0.0));
|
||||
}
|
||||
points
|
||||
}
|
||||
|
||||
fn v3(point: DVec3) -> Vector3 {
|
||||
Vector3::new(point.x, point.y, point.z)
|
||||
}
|
||||
|
||||
fn nan() -> DVec3 {
|
||||
DVec3::splat(f64::NAN)
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<DVec3>) -> WireModel {
|
||||
WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
|
|
@ -173,11 +678,11 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
|||
render_instance: None,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: "dimangular_preview".to_string(),
|
||||
points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(),
|
||||
points: points.into_iter().map(|point| [point.x as f32, point.y as f32, point.z as f32]).collect(),
|
||||
points_low: Vec::new(),
|
||||
color: WireModel::CYAN,
|
||||
selected: false,
|
||||
|
|
@ -195,38 +700,4 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
|||
}
|
||||
}
|
||||
|
||||
fn angular_preview(vertex: Vec3, first: Vec3, second: Vec3, arc_pt: Vec3) -> Vec<Vec3> {
|
||||
let mut points = vec![
|
||||
vertex,
|
||||
first,
|
||||
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
|
||||
vertex,
|
||||
second,
|
||||
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
|
||||
];
|
||||
let r = vertex.distance(arc_pt);
|
||||
if r <= 1e-6 {
|
||||
return points;
|
||||
}
|
||||
let a0 = (first.y - vertex.y).atan2(first.x - vertex.x);
|
||||
let mut a1 = (second.y - vertex.y).atan2(second.x - vertex.x);
|
||||
let mut delta = a1 - a0;
|
||||
while delta <= 0.0 {
|
||||
delta += std::f32::consts::TAU;
|
||||
}
|
||||
if delta > std::f32::consts::PI {
|
||||
a1 -= std::f32::consts::TAU;
|
||||
delta = a1 - a0;
|
||||
}
|
||||
let steps = 24;
|
||||
for i in 0..=steps {
|
||||
let t = i as f32 / steps as f32;
|
||||
let a = a0 + delta * t;
|
||||
points.push(vertex + Vec3::new(a.cos() * r, a.sin() * r, 0.0));
|
||||
}
|
||||
points
|
||||
}
|
||||
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMANGULAR"] }); // AngularDimensionCommand
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMANGULAR"] });
|
||||
|
|
|
|||
|
|
@ -194,7 +194,10 @@ impl CadCommand for LinearDimensionCommand {
|
|||
Dimension::Linear(dim),
|
||||
));
|
||||
if let Some(source) = self.source_handle {
|
||||
CmdResult::CommitAssociativeDimension { entity, source }
|
||||
CmdResult::CommitAssociativeDimension {
|
||||
entity,
|
||||
sources: vec![source, source],
|
||||
}
|
||||
} else {
|
||||
CmdResult::CommitAndExit(entity)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ fn source_points(entity: &EntityType) -> Vec<Vector3> {
|
|||
match entity {
|
||||
EntityType::Line(line) => vec![line.start, line.end],
|
||||
EntityType::Arc(arc) => vec![arc.start_point_wcs(), arc.end_point_wcs()],
|
||||
EntityType::Circle(_) => Vec::new(),
|
||||
EntityType::LwPolyline(polyline) => polyline
|
||||
.vertices
|
||||
.iter()
|
||||
|
|
@ -55,6 +56,37 @@ fn source_points(entity: &EntityType) -> Vec<Vector3> {
|
|||
}
|
||||
}
|
||||
|
||||
fn source_reference(entity: &EntityType, point: Vector3) -> Option<(i32, f64)> {
|
||||
let curve_parameter = match entity {
|
||||
EntityType::Circle(circle) => {
|
||||
let center = circle.center_wcs();
|
||||
if point_distance_squared(center, point) <= 1e-16 {
|
||||
return Some((-3, 0.0));
|
||||
}
|
||||
let (axis_x, axis_y) = circle.axes_wcs();
|
||||
let offset = point - center;
|
||||
Some(offset.dot(&axis_y).atan2(offset.dot(&axis_x)))
|
||||
}
|
||||
EntityType::Arc(arc) => {
|
||||
let local = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(point.x, point.y, point.z),
|
||||
(arc.normal.x, arc.normal.y, arc.normal.z),
|
||||
);
|
||||
let dx = local.0 - arc.center.x;
|
||||
let dy = local.1 - arc.center.y;
|
||||
if dx * dx + dy * dy <= 1e-16 {
|
||||
return Some((-3, 0.0));
|
||||
}
|
||||
Some(dy.atan2(dx))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(parameter) = curve_parameter {
|
||||
return Some((-2, parameter));
|
||||
}
|
||||
source_marker(entity, point).map(|marker| (marker, 0.0))
|
||||
}
|
||||
|
||||
fn source_marker(entity: &EntityType, point: Vector3) -> Option<i32> {
|
||||
source_points(entity)
|
||||
.into_iter()
|
||||
|
|
@ -69,11 +101,45 @@ fn source_marker(entity: &EntityType, point: Vector3) -> Option<i32> {
|
|||
fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Option<Vector3> {
|
||||
let source = *reference.xrefs.first()?;
|
||||
let entity = scene.document.get_entity(source)?;
|
||||
if reference.main_gs_marker == -3 {
|
||||
return match entity {
|
||||
EntityType::Circle(circle) => Some(circle.center_wcs()),
|
||||
EntityType::Arc(arc) => Some(arc.center_wcs()),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
if reference.main_gs_marker == -2 {
|
||||
return match entity {
|
||||
EntityType::Circle(circle) => {
|
||||
Some(circle.point_at_angle_wcs(reference.osnap_distance))
|
||||
}
|
||||
EntityType::Arc(arc) => Some(arc.point_at_angle_wcs(reference.osnap_distance)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
source_points(entity)
|
||||
.get(reference.main_gs_marker.max(0) as usize)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn dimension_reference_points(dimension: &Dimension) -> Vec<Vector3> {
|
||||
match dimension {
|
||||
Dimension::Linear(linear) => vec![linear.first_point, linear.second_point],
|
||||
Dimension::Angular3Pt(angular) => vec![
|
||||
angular.angle_vertex,
|
||||
angular.first_point,
|
||||
angular.second_point,
|
||||
],
|
||||
Dimension::Angular2Ln(angular) => vec![
|
||||
angular.first_point,
|
||||
angular.second_point,
|
||||
angular.angle_vertex,
|
||||
angular.definition_point,
|
||||
],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_is_associative(
|
||||
document: &acadrust::CadDocument,
|
||||
dimension: Handle,
|
||||
|
|
@ -97,34 +163,41 @@ pub(crate) fn dimension_is_associative(
|
|||
}
|
||||
|
||||
impl Scene {
|
||||
pub(crate) fn attach_linear_dimension_association(
|
||||
pub(crate) fn attach_dimension_association(
|
||||
&mut self,
|
||||
dimension: Handle,
|
||||
sources: [Option<Handle>; 2],
|
||||
sources: Vec<Option<Handle>>,
|
||||
) {
|
||||
let Some(EntityType::Dimension(Dimension::Linear(linear))) =
|
||||
let Some(EntityType::Dimension(dimension_entity)) =
|
||||
self.document.get_entity(dimension)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let first_point = linear.first_point;
|
||||
let second_point = linear.second_point;
|
||||
let source_data = [first_point, second_point].map(|point| point);
|
||||
let resolved: [Option<(Handle, i32)>; 2] = std::array::from_fn(|index| {
|
||||
let source = sources[index]?;
|
||||
let source_data = dimension_reference_points(dimension_entity);
|
||||
if source_data.is_empty() {
|
||||
return;
|
||||
}
|
||||
let resolved: Vec<Option<(Handle, i32, f64)>> = source_data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, point)| {
|
||||
let source = sources.get(index).copied().flatten()?;
|
||||
let entity = self.document.get_entity(source)?;
|
||||
source_marker(entity, source_data[index]).map(|marker| (source, marker))
|
||||
});
|
||||
source_reference(entity, *point)
|
||||
.map(|(marker, parameter)| (source, marker, parameter))
|
||||
})
|
||||
.collect();
|
||||
if resolved.iter().all(Option::is_none) {
|
||||
return;
|
||||
}
|
||||
|
||||
let reference = |source: Handle, marker: i32, point: Vector3| AssocDimensionReference {
|
||||
let reference = |source: Handle, marker: i32, parameter: f64, point: Vector3| AssocDimensionReference {
|
||||
class_name: "AcDbOsnapPointRef".to_string(),
|
||||
osnap_type: 1,
|
||||
xrefs: vec![source],
|
||||
main_subent_type: 1,
|
||||
main_gs_marker: marker,
|
||||
osnap_distance: parameter,
|
||||
osnap_point: point,
|
||||
..AssocDimensionReference::default()
|
||||
};
|
||||
|
|
@ -132,9 +205,12 @@ impl Scene {
|
|||
std::array::from_fn(|_| Vec::new());
|
||||
let mut associativity = 0;
|
||||
for (index, resolved) in resolved.into_iter().enumerate() {
|
||||
if let Some((source, marker)) = resolved {
|
||||
if index >= references.len() {
|
||||
break;
|
||||
}
|
||||
if let Some((source, marker, parameter)) = resolved {
|
||||
associativity |= 1 << index;
|
||||
references[index].push(reference(source, marker, source_data[index]));
|
||||
references[index].push(reference(source, marker, parameter, source_data[index]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +241,14 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn attach_linear_dimension_association(
|
||||
&mut self,
|
||||
dimension: Handle,
|
||||
sources: [Option<Handle>; 2],
|
||||
) {
|
||||
self.attach_dimension_association(dimension, sources.into_iter().collect());
|
||||
}
|
||||
|
||||
pub(crate) fn infer_linear_dimension_sources(
|
||||
&self,
|
||||
dimension: Handle,
|
||||
|
|
@ -222,26 +306,57 @@ impl Scene {
|
|||
|
||||
let mut refreshed = Vec::new();
|
||||
for association in associations {
|
||||
let first = association.references[0]
|
||||
.first()
|
||||
.and_then(|reference| resolve_reference(self, reference));
|
||||
let second = association.references[1]
|
||||
.first()
|
||||
.and_then(|reference| resolve_reference(self, reference));
|
||||
if first.is_none() && second.is_none() {
|
||||
let resolved: [Option<Vector3>; 4] = std::array::from_fn(|index| {
|
||||
association.references[index]
|
||||
.first()
|
||||
.and_then(|reference| resolve_reference(self, reference))
|
||||
});
|
||||
if resolved.iter().all(Option::is_none) {
|
||||
continue;
|
||||
}
|
||||
if let Some(EntityType::Dimension(Dimension::Linear(linear))) =
|
||||
if let Some(EntityType::Dimension(dimension)) =
|
||||
self.document.get_entity_mut(association.dimension)
|
||||
{
|
||||
if let Some(first) = first {
|
||||
linear.first_point = first;
|
||||
match dimension {
|
||||
Dimension::Linear(linear) => {
|
||||
if let Some(point) = resolved[0] {
|
||||
linear.first_point = point;
|
||||
}
|
||||
if let Some(point) = resolved[1] {
|
||||
linear.second_point = point;
|
||||
}
|
||||
linear.base.definition_point = linear.definition_point;
|
||||
}
|
||||
Dimension::Angular3Pt(angular) => {
|
||||
if let Some(point) = resolved[0] {
|
||||
angular.angle_vertex = point;
|
||||
}
|
||||
if let Some(point) = resolved[1] {
|
||||
angular.first_point = point;
|
||||
}
|
||||
if let Some(point) = resolved[2] {
|
||||
angular.second_point = point;
|
||||
}
|
||||
angular.base.definition_point = angular.definition_point;
|
||||
}
|
||||
Dimension::Angular2Ln(angular) => {
|
||||
if let Some(point) = resolved[0] {
|
||||
angular.first_point = point;
|
||||
}
|
||||
if let Some(point) = resolved[1] {
|
||||
angular.second_point = point;
|
||||
}
|
||||
if let Some(point) = resolved[2] {
|
||||
angular.angle_vertex = point;
|
||||
}
|
||||
if let Some(point) = resolved[3] {
|
||||
angular.definition_point = point;
|
||||
}
|
||||
angular.base.definition_point = angular.dimension_arc;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
if let Some(second) = second {
|
||||
linear.second_point = second;
|
||||
}
|
||||
linear.base.actual_measurement = linear.measurement();
|
||||
linear.base.definition_point = linear.definition_point;
|
||||
dimension.base_mut().actual_measurement = dimension.measurement();
|
||||
refreshed.push((association.dimension, ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue