Complete arc length dimension workflow
This commit is contained in:
parent
bbd58eb9f2
commit
8aca9a0aef
8 changed files with 1026 additions and 15 deletions
|
|
@ -55,6 +55,13 @@ impl OpenCADStudio {
|
|||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
|
||||
"DIMARC" => {
|
||||
use crate::modules::annotate::arc_length_dim::ArcLengthDimensionCommand;
|
||||
let new_cmd = ArcLengthDimensionCommand::new();
|
||||
self.command_line.push_info(&new_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
|
||||
"DIMORDINATE" => {
|
||||
use crate::modules::annotate::ordinate_dim::OrdinateDimCommand;
|
||||
let new_cmd = OrdinateDimCommand::new();
|
||||
|
|
|
|||
|
|
@ -1725,7 +1725,16 @@ impl OpenCADStudio {
|
|||
_ => None,
|
||||
};
|
||||
if let Some((anno_field, insert_after)) = anno {
|
||||
let is_anno = crate::scene::annotative::is_annotative(doc, entity);
|
||||
let is_anno = crate::scene::annotative::is_annotative(doc, entity)
|
||||
|| match entity {
|
||||
acadrust::EntityType::Dimension(dimension) => {
|
||||
crate::scene::annotative::dim_style_is_annotative(
|
||||
doc,
|
||||
&dimension.base().style_name,
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
// Dimensions/tables carry no Annotative row yet — add one
|
||||
// right after their style row.
|
||||
if let Some(anchor) = insert_after {
|
||||
|
|
@ -1754,6 +1763,13 @@ impl OpenCADStudio {
|
|||
value: t.is_annotative,
|
||||
},
|
||||
),
|
||||
acadrust::EntityType::Dimension(
|
||||
acadrust::entities::Dimension::Arc(_),
|
||||
) => set_row(
|
||||
&mut sections,
|
||||
"annotative",
|
||||
if is_anno { "Yes" } else { "No" }.to_string(),
|
||||
),
|
||||
acadrust::EntityType::Text(_)
|
||||
| acadrust::EntityType::Insert(_)
|
||||
| acadrust::EntityType::Hatch(_)
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ pub fn property_int_code(field: &str) -> Option<i16> {
|
|||
"dim_tolerance_precision" => DIMTDEC,
|
||||
"dim_tolerance_pos_vert" => DIMTOLJ,
|
||||
"dim_tolerance_alignment" => DIMTALN,
|
||||
"dim_arc_symbol" => DIMARCSYM,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
@ -337,6 +338,7 @@ fn inherited_int(doc: &CadDocument, handle: Handle, code: i16) -> i16 {
|
|||
DIMADEC => style.dimadec,
|
||||
DIMTFILL => style.dimtfill,
|
||||
DIMTALN => 0,
|
||||
DIMARCSYM => style.dimarcsym,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -703,6 +705,12 @@ pub fn set_property(
|
|||
"move text, no leader" => Some(2),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_arc_symbol" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"preceding dimension text" => Some(0),
|
||||
"above dimension text" => Some(1),
|
||||
"none" => Some(2),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_alt_format" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"scientific" => Some(1),
|
||||
"decimal" => Some(2),
|
||||
|
|
|
|||
|
|
@ -175,6 +175,16 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
|
|||
],
|
||||
}];
|
||||
}
|
||||
if let Dimension::Arc(d) = dim {
|
||||
return vec![PropSection {
|
||||
title: t!("Misc").into_owned(),
|
||||
props: vec![Property {
|
||||
label: t!("Dimension style").into_owned(),
|
||||
field: "style_name",
|
||||
value: PropValue::PlainText(d.base.style_name.clone()),
|
||||
}],
|
||||
}];
|
||||
}
|
||||
let mut props = base_props(dim.base());
|
||||
match dim {
|
||||
Dimension::Aligned(d) => {
|
||||
|
|
@ -2598,6 +2608,33 @@ pub fn style_sections(
|
|||
}
|
||||
}
|
||||
|
||||
if matches!(dimension, Dimension::Arc(_)) {
|
||||
if let Some(lines) = sections
|
||||
.iter_mut()
|
||||
.find(|section| section.title == t!("Lines & Arrows").as_ref())
|
||||
{
|
||||
let symbol = match int(ov::DIMARCSYM, s.dimarcsym) {
|
||||
1 => "Above dimension text",
|
||||
2 => "None",
|
||||
_ => "Preceding dimension text",
|
||||
};
|
||||
lines.props.insert(
|
||||
3,
|
||||
choice(
|
||||
t!("Arc length symbol").as_ref(),
|
||||
"dim_arc_symbol",
|
||||
symbol,
|
||||
&[
|
||||
"Preceding dimension text",
|
||||
"Above dimension text",
|
||||
"None",
|
||||
],
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(dimension, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_)) {
|
||||
if let Some(text_section) = sections
|
||||
.iter_mut()
|
||||
|
|
@ -3405,10 +3442,8 @@ fn tessellate_dimension_inner(
|
|||
// DIMUPT governs interactive creation-time text placement; saved
|
||||
// geometry already carries the resulting position.
|
||||
let _ = s.dimupt;
|
||||
// DIMARCSYM only applies to arc-length dims; DIMJOGANG only to
|
||||
// jogged-radius dims. We don't ship those Dimension variants yet,
|
||||
// so the values are read for round-trip but not drawn.
|
||||
let _ = (s.dimarcsym, s.dimjogang);
|
||||
// DIMJOGANG is consumed by the jogged-radius path.
|
||||
let _ = s.dimjogang;
|
||||
// DIMUNIT is the obsolete pre-R2000 linear unit format; DIMLUNIT
|
||||
// supersedes it. Read but not honoured.
|
||||
let _ = s.dimunit;
|
||||
|
|
@ -3634,6 +3669,46 @@ fn tessellate_dimension_inner(
|
|||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
if let Some(symbol) = style.and_then(|style| {
|
||||
arc_length_symbol_points(dim, Some(style), dim_txt, dim_scale, style.dimarcsym)
|
||||
}) {
|
||||
let mut points = Vec::new();
|
||||
add_polyline(&mut points, &symbol);
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: name.clone(),
|
||||
points,
|
||||
points_low: Vec::new(),
|
||||
color: if selected { WireModel::SELECTED } else { text_color },
|
||||
selected,
|
||||
aci: 0,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
// DIMTFILL: 0=none, 1=drawing background (mask), 2=DIMTFILLCLR.
|
||||
if let Some(s) = style {
|
||||
if s.dimtfill == 1 || s.dimtfill == 2 {
|
||||
|
|
@ -3935,6 +4010,57 @@ fn text_fill_rect(
|
|||
let p4 = corner(-hx, hy);
|
||||
Some(vec![p1, p2, p3, p1, p3, p4])
|
||||
}
|
||||
|
||||
fn arc_length_symbol_points(
|
||||
dim: &Dimension,
|
||||
style: Option<&DimStyle>,
|
||||
text_height: f64,
|
||||
dim_scale: f64,
|
||||
symbol_position: i16,
|
||||
) -> Option<Vec<Vec3>> {
|
||||
if !matches!(dim, Dimension::Arc(_)) || symbol_position == 2 {
|
||||
return None;
|
||||
}
|
||||
let value = dimension_text_value(dim, style)?;
|
||||
if value.is_empty() || text_height <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let position = dimension_text_pos_f64(dim, style, text_height, dim_scale);
|
||||
let rotation = dimension_text_rotation(dim, style);
|
||||
let (sin_rotation, cos_rotation) = rotation.sin_cos();
|
||||
let text_width = value.chars().count() as f64 * text_height * 0.6;
|
||||
let symbol_width = text_height * 0.62;
|
||||
let symbol_height = text_height * 0.20;
|
||||
let (center_x, center_y) = if symbol_position == 1 {
|
||||
(0.0, text_height * 0.72)
|
||||
} else {
|
||||
(-(text_width * 0.5 + symbol_width * 0.70), text_height * 0.02)
|
||||
};
|
||||
let transform = |x: f64, y: f64| {
|
||||
let local_x = center_x + x;
|
||||
let local_y = center_y + y;
|
||||
Vec3::new(
|
||||
(position.x + local_x * cos_rotation - local_y * sin_rotation) as f32,
|
||||
(position.y + local_x * sin_rotation + local_y * cos_rotation) as f32,
|
||||
position.z as f32,
|
||||
)
|
||||
};
|
||||
|
||||
let steps = 8usize;
|
||||
Some(
|
||||
(0..=steps)
|
||||
.map(|index| {
|
||||
let t = index as f64 / steps as f64;
|
||||
let x = (t - 0.5) * symbol_width;
|
||||
let normalized = x / (symbol_width * 0.5);
|
||||
let y = symbol_height * (1.0 - normalized * normalized);
|
||||
transform(x, y)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
struct SuppressFlags {
|
||||
ext1: bool,
|
||||
ext2: bool,
|
||||
|
|
@ -4170,6 +4296,7 @@ fn dimension_geometry(
|
|||
}
|
||||
}
|
||||
Dimension::Arc(d) => {
|
||||
let explicit_sweep = arc_dimension_angles(d);
|
||||
append_angular_dimension(
|
||||
&mut g,
|
||||
lv(d.center_point),
|
||||
|
|
@ -4178,10 +4305,7 @@ fn dimension_geometry(
|
|||
lv(d.definition_point),
|
||||
arrow1,
|
||||
arrow2,
|
||||
d.is_partial.then_some((
|
||||
d.arc_start_parameter as f32,
|
||||
d.arc_end_parameter as f32,
|
||||
)),
|
||||
explicit_sweep,
|
||||
params,
|
||||
suppress,
|
||||
);
|
||||
|
|
@ -4585,6 +4709,25 @@ fn two_line_angle_frame(
|
|||
Some((vertex, start, end))
|
||||
}
|
||||
|
||||
pub(crate) fn arc_dimension_angles(dimension: &DimensionArc) -> Option<(f32, f32)> {
|
||||
let raw = dimension.arc_end_parameter - dimension.arc_start_parameter;
|
||||
let mut sweep = raw.rem_euclid(std::f64::consts::TAU);
|
||||
if sweep <= 1.0e-12 && raw.abs() > 1.0e-12 {
|
||||
sweep = std::f64::consts::TAU;
|
||||
}
|
||||
if sweep > 1.0e-12 {
|
||||
let start = dimension.arc_start_parameter as f32;
|
||||
return Some((start, start + sweep as f32));
|
||||
}
|
||||
|
||||
let start = (dimension.first_extension_point.y - dimension.center_point.y)
|
||||
.atan2(dimension.first_extension_point.x - dimension.center_point.x);
|
||||
let end = (dimension.second_extension_point.y - dimension.center_point.y)
|
||||
.atan2(dimension.second_extension_point.x - dimension.center_point.x);
|
||||
let sweep = (end - start).rem_euclid(std::f64::consts::TAU);
|
||||
(sweep > 1.0e-12).then_some((start as f32, (start + sweep) as f32))
|
||||
}
|
||||
|
||||
fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
|
||||
let (vertex, start, end, arc_point) = match dim {
|
||||
Dimension::Angular2Ln(value) => {
|
||||
|
|
@ -4611,6 +4754,12 @@ fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
|
|||
two_line_angle_frame(vertex, first, vertex, second, arc_point)?;
|
||||
(vertex, start, end, arc_point)
|
||||
}
|
||||
Dimension::Arc(value) => {
|
||||
let vertex = vec3_local(value.center_point);
|
||||
let arc_point = vec3_local(value.definition_point);
|
||||
let (start, end) = arc_dimension_angles(value)?;
|
||||
(vertex, start, end, arc_point)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let radius = vertex.distance(arc_point);
|
||||
|
|
@ -5201,7 +5350,7 @@ fn dimension_text_natural_rotation(dim: &Dimension) -> f64 {
|
|||
let dy = d.second_point.y - d.first_point.y;
|
||||
dy.atan2(dx)
|
||||
}
|
||||
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => angular_dimension_frame(dim)
|
||||
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) | Dimension::Arc(_) => angular_dimension_frame(dim)
|
||||
.map(|(_, start, end, _)| {
|
||||
((start + end) * 0.5 + std::f32::consts::FRAC_PI_2) as f64
|
||||
})
|
||||
|
|
@ -6135,6 +6284,45 @@ pub(crate) fn baked_dimension_text_entity(
|
|||
Some(ent)
|
||||
}
|
||||
|
||||
pub(crate) fn baked_arc_length_symbol_points(
|
||||
dim: &Dimension,
|
||||
document: &CadDocument,
|
||||
anno_scale: f64,
|
||||
) -> Vec<Vector3> {
|
||||
if !matches!(dim, Dimension::Arc(_)) {
|
||||
return Vec::new();
|
||||
}
|
||||
let style_name = dim.base().style_name.as_str();
|
||||
let style = document.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(style_name)
|
||||
|| (style_name.trim().is_empty() && style.name.eq_ignore_ascii_case("Standard"))
|
||||
});
|
||||
let dim_scale = style
|
||||
.map(|style| {
|
||||
if style.dimscale > 1.0e-6 {
|
||||
style.dimscale
|
||||
} else {
|
||||
anno_scale
|
||||
}
|
||||
})
|
||||
.unwrap_or(1.0);
|
||||
let text_height = style
|
||||
.map(|style| style.dimtxt * dim_scale)
|
||||
.unwrap_or(2.5 * dim_scale);
|
||||
let symbol_position = crate::entities::dim_override::int(
|
||||
&dim.base().common.extended_data,
|
||||
crate::entities::dim_override::DIMARCSYM,
|
||||
)
|
||||
.or_else(|| style.map(|style| style.dimarcsym))
|
||||
.unwrap_or(0);
|
||||
|
||||
arc_length_symbol_points(dim, style, text_height, dim_scale, symbol_position)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|point| Vector3::new(point.x as f64, point.y as f64, point.z as f64))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_text_grip_position(
|
||||
dim: &Dimension,
|
||||
document: &CadDocument,
|
||||
|
|
|
|||
638
src/modules/annotate/arc_length_dim.rs
Normal file
638
src/modules/annotate/arc_length_dim.rs
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
use acadrust::entities::{Dimension, DimensionArc};
|
||||
use acadrust::types::{Handle, Vector3};
|
||||
use acadrust::EntityType;
|
||||
use cadkernel::geom2d::tessellate::{arc, DEFAULT_SEGMENTS_PER_RADIAN};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{
|
||||
CadCommand, CmdOption, CmdResult, DimensionAssociationInput,
|
||||
DimensionAssociationSource, WorkingPlane,
|
||||
};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
use crate::scene::dimension_assoc::{
|
||||
polyline_arc_point_marker, RadialSourceGeometry,
|
||||
POLYLINE_ARC_CENTER_MARKER,
|
||||
};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
pub const ICON: IconKind =
|
||||
IconKind::Svg(include_bytes!("../../../assets/icons/dim_angular.svg"));
|
||||
|
||||
pub fn tool() -> ToolDef {
|
||||
ToolDef {
|
||||
id: "DIMARC",
|
||||
label: "Arc Length",
|
||||
icon: ICON,
|
||||
event: ModuleEvent::Command("DIMARC".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SourceBinding {
|
||||
Arc,
|
||||
PolylineSegment(i32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ArcSelection {
|
||||
source: RadialSourceGeometry,
|
||||
binding: SourceBinding,
|
||||
start_angle: f64,
|
||||
end_angle: f64,
|
||||
is_partial: bool,
|
||||
}
|
||||
|
||||
impl ArcSelection {
|
||||
fn new(source: RadialSourceGeometry, binding: SourceBinding) -> Option<Self> {
|
||||
let sweep = positive_sweep(source.start_angle, source.end_angle);
|
||||
(source.limited && source.radius > 1.0e-12 && sweep > 1.0e-12).then_some(Self {
|
||||
source,
|
||||
binding,
|
||||
start_angle: source.start_angle,
|
||||
end_angle: source.start_angle + sweep,
|
||||
is_partial: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn sweep(self) -> f64 {
|
||||
positive_sweep(self.start_angle, self.end_angle)
|
||||
}
|
||||
|
||||
fn point_at(self, angle: f64) -> DVec3 {
|
||||
dvec(self.source.point_at_angle(angle))
|
||||
}
|
||||
|
||||
fn center(self) -> DVec3 {
|
||||
dvec(self.source.center_world())
|
||||
}
|
||||
|
||||
fn plane(self) -> WorkingPlane {
|
||||
WorkingPlane::new(
|
||||
DVec3::from_array(self.source.plane.origin),
|
||||
DVec3::from_array(self.source.plane.x_axis),
|
||||
DVec3::from_array(self.source.plane.y_axis),
|
||||
)
|
||||
}
|
||||
|
||||
fn clamped_angle(self, point: DVec3) -> f64 {
|
||||
let raw = self.source.angle_at(point.to_array());
|
||||
let relative = (raw - self.start_angle).rem_euclid(std::f64::consts::TAU);
|
||||
let sweep = self.sweep();
|
||||
if relative <= sweep + 1.0e-10 {
|
||||
return self.start_angle + relative.min(sweep);
|
||||
}
|
||||
let start_distance = relative.min(std::f64::consts::TAU - relative);
|
||||
let end_relative = (raw - self.end_angle).rem_euclid(std::f64::consts::TAU);
|
||||
let end_distance = end_relative.min(std::f64::consts::TAU - end_relative);
|
||||
if start_distance <= end_distance {
|
||||
self.start_angle
|
||||
} else {
|
||||
self.end_angle
|
||||
}
|
||||
}
|
||||
|
||||
fn with_partial(self, first: f64, second: f64) -> Option<Self> {
|
||||
let first = (first - self.start_angle)
|
||||
.rem_euclid(std::f64::consts::TAU)
|
||||
.clamp(0.0, self.sweep());
|
||||
let second = (second - self.start_angle)
|
||||
.rem_euclid(std::f64::consts::TAU)
|
||||
.clamp(0.0, self.sweep());
|
||||
let (start, end) = if first <= second {
|
||||
(first, second)
|
||||
} else {
|
||||
(second, first)
|
||||
};
|
||||
(end - start > 1.0e-12).then_some(Self {
|
||||
start_angle: self.start_angle + start,
|
||||
end_angle: self.start_angle + end,
|
||||
is_partial: true,
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
fn association_sources(self, handle: Handle) -> Vec<Option<DimensionAssociationSource>> {
|
||||
match self.binding {
|
||||
SourceBinding::Arc => vec![
|
||||
Some(DimensionAssociationSource::explicit(handle, -3, 0.0)),
|
||||
Some(DimensionAssociationSource::explicit(
|
||||
handle,
|
||||
-2,
|
||||
self.start_angle,
|
||||
)),
|
||||
Some(DimensionAssociationSource::explicit(
|
||||
handle,
|
||||
-2,
|
||||
self.end_angle,
|
||||
)),
|
||||
],
|
||||
SourceBinding::PolylineSegment(segment) => {
|
||||
let point_marker = polyline_arc_point_marker(segment);
|
||||
vec![
|
||||
Some(DimensionAssociationSource::explicit(
|
||||
handle,
|
||||
POLYLINE_ARC_CENTER_MARKER,
|
||||
segment as f64,
|
||||
)),
|
||||
Some(DimensionAssociationSource::explicit(
|
||||
handle,
|
||||
point_marker,
|
||||
self.start_angle,
|
||||
)),
|
||||
Some(DimensionAssociationSource::explicit(
|
||||
handle,
|
||||
point_marker,
|
||||
self.end_angle,
|
||||
)),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Step {
|
||||
SelectObject,
|
||||
DimLine(ArcSelection),
|
||||
PartialFirst(ArcSelection),
|
||||
PartialSecond {
|
||||
selection: ArcSelection,
|
||||
first_angle: f64,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct ArcLengthDimensionCommand {
|
||||
step: Step,
|
||||
picked_entity: Option<EntityType>,
|
||||
source_handle: Option<Handle>,
|
||||
text_override: Option<String>,
|
||||
awaiting_text: bool,
|
||||
text_angle: Option<f64>,
|
||||
awaiting_angle: bool,
|
||||
leader_enabled: bool,
|
||||
}
|
||||
|
||||
impl ArcLengthDimensionCommand {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: Step::SelectObject,
|
||||
picked_entity: None,
|
||||
source_handle: None,
|
||||
text_override: None,
|
||||
awaiting_text: false,
|
||||
text_angle: None,
|
||||
awaiting_angle: false,
|
||||
leader_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn editor_anchor(&self) -> DVec3 {
|
||||
match self.step {
|
||||
Step::SelectObject => DVec3::ZERO,
|
||||
Step::DimLine(selection)
|
||||
| Step::PartialFirst(selection)
|
||||
| Step::PartialSecond { selection, .. } => {
|
||||
selection.point_at(selection.start_angle + selection.sweep() * 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn commit_dimension(&self, selection: ArcSelection, point: DVec3) -> CmdResult {
|
||||
let plane = selection.plane();
|
||||
let center = plane.to_local(selection.center());
|
||||
let first = plane.to_local(selection.point_at(selection.start_angle));
|
||||
let second = plane.to_local(selection.point_at(selection.end_angle));
|
||||
let picked = plane.to_local(point);
|
||||
let picked_radius = (picked - center).truncate().length();
|
||||
if !picked_radius.is_finite() || picked_radius <= 1.0e-12 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
|
||||
let mut dimension = DimensionArc::default();
|
||||
dimension.center_point = v3(center);
|
||||
dimension.first_extension_point = v3(first);
|
||||
dimension.second_extension_point = v3(second);
|
||||
dimension.definition_point = v3(picked);
|
||||
dimension.is_partial = selection.is_partial;
|
||||
dimension.arc_start_parameter = selection.start_angle;
|
||||
dimension.arc_end_parameter = selection.end_angle;
|
||||
dimension.base.definition_point = dimension.definition_point;
|
||||
dimension.base.text_middle_point = dimension.definition_point;
|
||||
dimension.base.insertion_point = dimension.definition_point;
|
||||
dimension.has_leader = self.leader_enabled && selection.sweep() > std::f64::consts::FRAC_PI_2;
|
||||
if dimension.has_leader {
|
||||
let middle = selection.start_angle + selection.sweep() * 0.5;
|
||||
let anchor = DVec3::new(
|
||||
center.x + picked_radius * middle.cos(),
|
||||
center.y + picked_radius * middle.sin(),
|
||||
picked.z,
|
||||
);
|
||||
dimension.first_leader_point = v3(anchor);
|
||||
dimension.second_leader_point = v3(picked);
|
||||
dimension.definition_point = v3(anchor);
|
||||
dimension.base.definition_point = dimension.definition_point;
|
||||
dimension.base.text_user_positioned = true;
|
||||
}
|
||||
dimension.base.actual_measurement = dimension.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(
|
||||
&mut dimension.base,
|
||||
self.text_override.clone(),
|
||||
);
|
||||
if let Some(angle) = self.text_angle {
|
||||
dimension.base.text_rotation = angle;
|
||||
}
|
||||
|
||||
let association = self.source_handle.map_or_else(
|
||||
|| DimensionAssociationInput::Explicit(Vec::new()),
|
||||
|handle| {
|
||||
DimensionAssociationInput::Explicit(
|
||||
selection.association_sources(handle),
|
||||
)
|
||||
},
|
||||
);
|
||||
CmdResult::CommitDimension {
|
||||
entity: plane.place_entity(EntityType::Dimension(Dimension::Arc(dimension))),
|
||||
association,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for ArcLengthDimensionCommand {
|
||||
fn set_working_plane(&mut self, _plane: WorkingPlane) {}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"DIMARC"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if self.awaiting_text {
|
||||
return "DIMARC Enter dimension text (blank = measured value):".to_string();
|
||||
}
|
||||
if self.awaiting_angle {
|
||||
return "DIMARC Specify text angle (degrees):".to_string();
|
||||
}
|
||||
match self.step {
|
||||
Step::SelectObject => {
|
||||
"DIMARC Select arc or polyline arc segment:".to_string()
|
||||
}
|
||||
Step::DimLine(selection) => {
|
||||
let leader_option = if selection.sweep() > std::f64::consts::FRAC_PI_2 {
|
||||
if self.leader_enabled { "/No Leader" } else { "/Leader" }
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!(
|
||||
"DIMARC Specify arc length dimension location [Mtext/Text/Angle/Partial{leader_option}]:"
|
||||
)
|
||||
}
|
||||
Step::PartialFirst(_) => {
|
||||
"DIMARC Specify first point of partial arc:".to_string()
|
||||
}
|
||||
Step::PartialSecond { .. } => {
|
||||
"DIMARC Specify second point of partial arc:".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::SelectObject => CmdResult::NeedPoint,
|
||||
Step::DimLine(selection) => self.commit_dimension(selection, point),
|
||||
Step::PartialFirst(selection) => {
|
||||
self.step = Step::PartialSecond {
|
||||
selection,
|
||||
first_angle: selection.clamped_angle(point),
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::PartialSecond {
|
||||
selection,
|
||||
first_angle,
|
||||
} => {
|
||||
let second_angle = selection.clamped_angle(point);
|
||||
let Some(partial) = selection.with_partial(first_angle, second_angle) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
self.leader_enabled = false;
|
||||
self.step = Step::DimLine(partial);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
!self.awaiting_text && !self.awaiting_angle
|
||||
}
|
||||
|
||||
fn wants_text_with_spaces(&self) -> bool {
|
||||
self.awaiting_text
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
let Step::DimLine(selection) = self.step else {
|
||||
return Vec::new();
|
||||
};
|
||||
if self.awaiting_text || self.awaiting_angle {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut options = vec![
|
||||
CmdOption::new("MText", "MTEXT"),
|
||||
CmdOption::new("Text", "TEXT"),
|
||||
CmdOption::new("Angle", "ANGLE"),
|
||||
CmdOption::new("Partial", "PARTIAL"),
|
||||
];
|
||||
if selection.sweep() > std::f64::consts::FRAC_PI_2 {
|
||||
options.push(if self.leader_enabled {
|
||||
CmdOption::new("No Leader", "NOLEADER")
|
||||
} else {
|
||||
CmdOption::new("Leader", "LEADER")
|
||||
});
|
||||
}
|
||||
options
|
||||
}
|
||||
|
||||
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 {
|
||||
let value = text.trim();
|
||||
self.text_angle = if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
crate::entities::common::parse_typed_angle(value)
|
||||
};
|
||||
self.awaiting_angle = false;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
let Step::DimLine(selection) = self.step else {
|
||||
return None;
|
||||
};
|
||||
match text.trim().to_ascii_uppercase().as_str() {
|
||||
"M" | "MTEXT" => Some(CmdResult::SuspendForMTextInput {
|
||||
pos: self.editor_anchor(),
|
||||
initial: self.text_override.clone().unwrap_or_default(),
|
||||
height: 2.5,
|
||||
}),
|
||||
"T" | "TEXT" => {
|
||||
self.awaiting_text = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"A" | "ANGLE" => {
|
||||
self.awaiting_angle = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"P" | "PARTIAL" => {
|
||||
self.step = Step::PartialFirst(selection);
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"L" | "LEADER" if selection.sweep() > std::f64::consts::FRAC_PI_2 => {
|
||||
self.leader_enabled = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"N" | "NOLEADER" => {
|
||||
self.leader_enabled = false;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_editor_text(&mut self, value: String) {
|
||||
let value = value.trim();
|
||||
self.text_override = if value.is_empty() || value == "<>" {
|
||||
None
|
||||
} else {
|
||||
Some(value.to_string())
|
||||
};
|
||||
}
|
||||
|
||||
fn on_editor_closed(&mut self, _committed: bool) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
matches!(self.step, Step::SelectObject)
|
||||
}
|
||||
|
||||
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 {
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let Some(source) = crate::scene::dimension_assoc::radial_source_at(
|
||||
&entity,
|
||||
Vector3::new(point.x, point.y, point.z),
|
||||
) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
if !source.limited {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
let binding = match entity {
|
||||
EntityType::Arc(_) => SourceBinding::Arc,
|
||||
EntityType::LwPolyline(_) | EntityType::Polyline2D(_) => {
|
||||
SourceBinding::PolylineSegment(source.marker)
|
||||
}
|
||||
_ => return CmdResult::NeedPoint,
|
||||
};
|
||||
let Some(selection) = ArcSelection::new(source, binding) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
self.source_handle = Some(handle);
|
||||
self.step = Step::DimLine(selection);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, point: DVec3) -> Option<WireModel> {
|
||||
match self.step {
|
||||
Step::SelectObject | Step::PartialFirst(_) => None,
|
||||
Step::DimLine(selection) => Some(dimension_preview(
|
||||
selection,
|
||||
point,
|
||||
self.leader_enabled,
|
||||
)),
|
||||
Step::PartialSecond {
|
||||
selection,
|
||||
first_angle,
|
||||
} => {
|
||||
let second_angle = selection.clamped_angle(point);
|
||||
let partial = selection.with_partial(first_angle, second_angle)?;
|
||||
Some(partial_preview(partial))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn positive_sweep(start: f64, end: f64) -> f64 {
|
||||
let raw = end - start;
|
||||
let mut sweep = raw.rem_euclid(std::f64::consts::TAU);
|
||||
if sweep <= 1.0e-12 && raw.abs() > 1.0e-12 {
|
||||
sweep = std::f64::consts::TAU;
|
||||
}
|
||||
sweep
|
||||
}
|
||||
|
||||
fn dimension_preview(
|
||||
selection: ArcSelection,
|
||||
point: DVec3,
|
||||
leader: bool,
|
||||
) -> WireModel {
|
||||
let plane = selection.plane();
|
||||
let center = plane.to_local(selection.center());
|
||||
let first = plane.to_local(selection.point_at(selection.start_angle));
|
||||
let second = plane.to_local(selection.point_at(selection.end_angle));
|
||||
let point = plane.to_local(point);
|
||||
let radius = (point - center).truncate().length();
|
||||
if radius <= 1.0e-12 {
|
||||
return preview_wire(Vec::new(), "dimarc_preview");
|
||||
}
|
||||
let start_land = DVec3::new(
|
||||
center.x + radius * selection.start_angle.cos(),
|
||||
center.y + radius * selection.start_angle.sin(),
|
||||
point.z,
|
||||
);
|
||||
let end_land = DVec3::new(
|
||||
center.x + radius * selection.end_angle.cos(),
|
||||
center.y + radius * selection.end_angle.sin(),
|
||||
point.z,
|
||||
);
|
||||
let mut points = vec![first, start_land, nan(), second, end_land, nan()];
|
||||
points.extend(
|
||||
arc(
|
||||
[center.x, center.y],
|
||||
radius,
|
||||
selection.start_angle,
|
||||
selection.end_angle,
|
||||
point.z,
|
||||
DEFAULT_SEGMENTS_PER_RADIAN,
|
||||
)
|
||||
.into_iter()
|
||||
.map(DVec3::from_array),
|
||||
);
|
||||
if leader && selection.sweep() > std::f64::consts::FRAC_PI_2 {
|
||||
let middle = selection.start_angle + selection.sweep() * 0.5;
|
||||
points.extend([
|
||||
nan(),
|
||||
DVec3::new(
|
||||
center.x + radius * middle.cos(),
|
||||
center.y + radius * middle.sin(),
|
||||
point.z,
|
||||
),
|
||||
point,
|
||||
]);
|
||||
}
|
||||
preview_wire(
|
||||
points
|
||||
.into_iter()
|
||||
.map(|value| if value.is_nan() { value } else { plane.to_world(value) })
|
||||
.collect(),
|
||||
"dimarc_preview",
|
||||
)
|
||||
}
|
||||
|
||||
fn partial_preview(selection: ArcSelection) -> WireModel {
|
||||
let points = arc(
|
||||
selection.source.center,
|
||||
selection.source.radius,
|
||||
selection.start_angle,
|
||||
selection.end_angle,
|
||||
0.0,
|
||||
DEFAULT_SEGMENTS_PER_RADIAN,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|point| DVec3::from_array(selection.source.plane.point_at([point[0], point[1]])))
|
||||
.collect();
|
||||
preview_wire(points, "dimarc_partial_preview")
|
||||
}
|
||||
|
||||
fn v3(point: DVec3) -> Vector3 {
|
||||
Vector3::new(point.x, point.y, point.z)
|
||||
}
|
||||
|
||||
fn dvec(point: Vector3) -> DVec3 {
|
||||
DVec3::new(point.x, point.y, point.z)
|
||||
}
|
||||
|
||||
fn nan() -> DVec3 {
|
||||
DVec3::splat(f64::NAN)
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<DVec3>, name: &str) -> WireModel {
|
||||
WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: name.to_string(),
|
||||
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,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
aci: 0,
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMARC"] });
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Annotate module — dimension, text, leader, table, and markup tools.
|
||||
|
||||
pub mod aligned_dim;
|
||||
pub mod arc_length_dim;
|
||||
pub mod angular_dim;
|
||||
pub mod data_extract;
|
||||
pub mod data_link;
|
||||
|
|
@ -98,6 +99,11 @@ impl CadModule for AnnotateModule {
|
|||
angular_dim::tool().label,
|
||||
angular_dim::tool().icon,
|
||||
),
|
||||
(
|
||||
arc_length_dim::tool().id,
|
||||
arc_length_dim::tool().label,
|
||||
arc_length_dim::tool().icon,
|
||||
),
|
||||
(
|
||||
radius_dim::tool().id,
|
||||
radius_dim::tool().label,
|
||||
|
|
|
|||
|
|
@ -1190,6 +1190,8 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
|
|||
}
|
||||
}
|
||||
Dimension::Arc(d) => {
|
||||
let explicit_sweep = crate::entities::dimension::arc_dimension_angles(d)
|
||||
.map(|(start, end)| (start as f64, end as f64));
|
||||
result.extend(angular_block_segs(
|
||||
d.center_point,
|
||||
d.first_extension_point,
|
||||
|
|
@ -1198,10 +1200,7 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
|
|||
&met,
|
||||
&ext_c,
|
||||
&dim_c,
|
||||
d.is_partial.then_some((
|
||||
d.arc_start_parameter,
|
||||
d.arc_end_parameter,
|
||||
)),
|
||||
explicit_sweep,
|
||||
));
|
||||
if d.has_leader {
|
||||
result.push(make_seg(
|
||||
|
|
@ -1235,6 +1234,15 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
|
|||
}
|
||||
}
|
||||
|
||||
let symbol_points =
|
||||
crate::entities::dimension::baked_arc_length_symbol_points(dim, doc, 1.0);
|
||||
if symbol_points.len() > 1 {
|
||||
let text_c = dim_common(&base.common, met.dimclrt, -2);
|
||||
for pair in symbol_points.windows(2) {
|
||||
result.push(make_seg(&pair[0], &pair[1], &text_c));
|
||||
}
|
||||
}
|
||||
|
||||
// Measurement text is the live render's own Text/MText entity (value,
|
||||
// position, height, rotation, alignment, text style, MText handling all
|
||||
// shared), so the baked block matches the on-screen dimension and the label
|
||||
|
|
|
|||
|
|
@ -16,6 +16,18 @@ use crate::command::DimensionAssociationSource;
|
|||
|
||||
use super::{ChangeKind, Scene};
|
||||
|
||||
pub(crate) const POLYLINE_ARC_CENTER_MARKER: i32 = -4;
|
||||
const POLYLINE_ARC_POINT_MARKER_BASE: i32 = -5;
|
||||
|
||||
pub(crate) fn polyline_arc_point_marker(segment: i32) -> i32 {
|
||||
POLYLINE_ARC_POINT_MARKER_BASE - segment.max(0)
|
||||
}
|
||||
|
||||
fn polyline_arc_segment_from_point_marker(marker: i32) -> Option<i32> {
|
||||
(marker <= POLYLINE_ARC_POINT_MARKER_BASE)
|
||||
.then_some(POLYLINE_ARC_POINT_MARKER_BASE - marker)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct RadialSourceGeometry {
|
||||
pub plane: Plane,
|
||||
|
|
@ -329,7 +341,13 @@ 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 == -4 {
|
||||
if let Some(segment) =
|
||||
polyline_arc_segment_from_point_marker(reference.main_gs_marker)
|
||||
{
|
||||
let radial = radial_source_for_marker(entity, segment)?;
|
||||
return Some(radial.point_at_angle(reference.osnap_distance));
|
||||
}
|
||||
if reference.main_gs_marker == POLYLINE_ARC_CENTER_MARKER {
|
||||
let segment = reference.osnap_distance.round().max(0.0) as usize;
|
||||
return polyline_arc_center(entity, segment);
|
||||
}
|
||||
|
|
@ -422,10 +440,55 @@ fn dimension_reference_points(dimension: &Dimension) -> Vec<Vector3> {
|
|||
angular.definition_point,
|
||||
],
|
||||
Dimension::Ordinate(ordinate) => vec![ordinate.feature_location],
|
||||
Dimension::Arc(arc) => vec![
|
||||
arc.center_point,
|
||||
arc.first_extension_point,
|
||||
arc.second_extension_point,
|
||||
],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn positive_sweep(start: f64, end: f64) -> f64 {
|
||||
let raw = end - start;
|
||||
let mut sweep = raw.rem_euclid(TAU);
|
||||
if sweep <= 1.0e-12 && raw.abs() > 1.0e-12 {
|
||||
sweep = TAU;
|
||||
}
|
||||
sweep
|
||||
}
|
||||
|
||||
fn signed_angle_delta(value: f64) -> f64 {
|
||||
(value + std::f64::consts::PI).rem_euclid(TAU) - std::f64::consts::PI
|
||||
}
|
||||
|
||||
fn angle_about_plane(
|
||||
plane: Plane,
|
||||
center: Vector3,
|
||||
point: Vector3,
|
||||
) -> f64 {
|
||||
let delta = [
|
||||
point.x - center.x,
|
||||
point.y - center.y,
|
||||
point.z - center.z,
|
||||
];
|
||||
let dot = |axis: [f64; 3]| {
|
||||
delta[0] * axis[0] + delta[1] * axis[1] + delta[2] * axis[2]
|
||||
};
|
||||
dot(plane.y_axis).atan2(dot(plane.x_axis))
|
||||
}
|
||||
|
||||
fn point_on_radial_circle(
|
||||
radial: RadialSourceGeometry,
|
||||
radius: f64,
|
||||
angle: f64,
|
||||
) -> Vector3 {
|
||||
vector3(radial.plane.point_at([
|
||||
radial.center[0] + radius * angle.cos(),
|
||||
radial.center[1] + radius * angle.sin(),
|
||||
]))
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_is_associative(
|
||||
document: &acadrust::CadDocument,
|
||||
dimension: Handle,
|
||||
|
|
@ -796,6 +859,18 @@ impl Scene {
|
|||
let radial = radial_source_for_marker(entity, reference.main_gs_marker)?;
|
||||
Some((radial, reference.osnap_distance))
|
||||
});
|
||||
let arc_source = association.references[0].first().and_then(|reference| {
|
||||
let source = *reference.xrefs.first()?;
|
||||
let entity = self.document.get_entity(source)?;
|
||||
let segment = match reference.main_gs_marker {
|
||||
-3 => 0,
|
||||
POLYLINE_ARC_CENTER_MARKER => {
|
||||
reference.osnap_distance.round().max(0.0) as i32
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
radial_source_for_marker(entity, segment)
|
||||
});
|
||||
if radial_source.is_none() && resolved.iter().all(Option::is_none) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -898,6 +973,71 @@ impl Scene {
|
|||
}
|
||||
ordinate.refresh_measurement();
|
||||
}
|
||||
Dimension::Arc(arc) => {
|
||||
let Some(radial) = arc_source else {
|
||||
continue;
|
||||
};
|
||||
let center = resolved[0].unwrap_or_else(|| radial.center_world());
|
||||
let Some(first) = resolved[1] else {
|
||||
continue;
|
||||
};
|
||||
let Some(second) = resolved[2] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let old_center = arc.center_point;
|
||||
let old_definition = arc.definition_point;
|
||||
let old_source_radius = old_center.distance(&arc.first_extension_point);
|
||||
let old_dim_radius = old_center.distance(&old_definition);
|
||||
let radial_offset = old_dim_radius - old_source_radius;
|
||||
let old_mid = arc.arc_start_parameter
|
||||
+ positive_sweep(
|
||||
arc.arc_start_parameter,
|
||||
arc.arc_end_parameter,
|
||||
) * 0.5;
|
||||
let old_definition_angle =
|
||||
angle_about_plane(radial.plane, old_center, old_definition);
|
||||
let definition_angle_offset =
|
||||
signed_angle_delta(old_definition_angle - old_mid);
|
||||
|
||||
let start = radial.angle_at(dpoint(first));
|
||||
let end_at = radial.angle_at(dpoint(second));
|
||||
let sweep = positive_sweep(start, end_at);
|
||||
let end = start + sweep;
|
||||
let new_radius = center.distance(&first);
|
||||
if !new_radius.is_finite() || new_radius <= 1.0e-12 {
|
||||
continue;
|
||||
}
|
||||
let dim_radius = (new_radius + radial_offset).max(1.0e-9);
|
||||
let middle = start + sweep * 0.5;
|
||||
let definition = point_on_radial_circle(
|
||||
radial,
|
||||
dim_radius,
|
||||
middle + definition_angle_offset,
|
||||
);
|
||||
let delta = definition - old_definition;
|
||||
|
||||
arc.center_point = center;
|
||||
arc.first_extension_point = first;
|
||||
arc.second_extension_point = second;
|
||||
arc.arc_start_parameter = start;
|
||||
arc.arc_end_parameter = end;
|
||||
arc.definition_point = definition;
|
||||
arc.base.definition_point = definition;
|
||||
if arc.base.text_user_positioned {
|
||||
arc.base.text_middle_point = arc.base.text_middle_point + delta;
|
||||
arc.base.insertion_point = arc.base.insertion_point + delta;
|
||||
} else {
|
||||
arc.base.text_middle_point = definition;
|
||||
arc.base.insertion_point = definition;
|
||||
}
|
||||
if arc.has_leader {
|
||||
arc.first_leader_point =
|
||||
point_on_radial_circle(radial, dim_radius, middle);
|
||||
arc.second_leader_point = arc.second_leader_point + delta;
|
||||
}
|
||||
arc.base.actual_measurement = arc.measurement();
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
dimension.base_mut().actual_measurement = dimension.measurement();
|
||||
|
|
|
|||
Loading…
Reference in a new issue