forked from 1b-agent/opencadstudio-upstream
Merge pull request #1447 from ramox81:codex/diameter-constraint-parity
#1447 branched from the same commit as #1443 and rewrote the radial dimensional constraint a second time, so the two designs had to be reconciled rather than stacked. #1447's is the one kept: it covers DCRADIUS and DCDIAMETER through one command, places the dimension by the direction the location was picked in rather than storing a text point, and names the parameters `radN` / `diaN`. #1443's radial command result, its entity builder call and its follow-up code are gone; its own independent work stays — the ribbon's small-button grid and tool descriptions, the magnitude reading of a negative radius, and the centre anchor a parameter edit resizes a circle about. The DIMCONSTRAINT default follows #1443: running DCRADIUS or DCDIAMETER directly leaves it alone, and the Dispatch arm sets it when DIMCONSTRAINT is what asked for the command. The second commit's radial drawing — the dimension line broken around the text, the elbowed leader, the centre-mark and arrowhead rules — applied to every radius and diameter dimension, not just a constraint's, and three layout-parity tests said the old layout was the measured one. It is now scoped to a dimensional constraint's own dimension, as #1443 scoped its version, and ordinary radial dimensions draw as they did. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
commit
889a19c362
9 files changed
+706
-415
No files matched your search
+172
-166
@@ -1935,6 +1935,7 @@ impl OpenCADStudio {
|
||||
dov::DIMEXO,
|
||||
dov::DIMEXE,
|
||||
dov::DIMASZ,
|
||||
dov::DIMTAD,
|
||||
dov::DIMTIH,
|
||||
dov::DIMTOH,
|
||||
] {
|
||||
@@ -4337,6 +4338,177 @@ impl OpenCADStudio {
|
||||
self.command_line.push_error(&message);
|
||||
return self.apply_cmd_result(CmdResult::Cancel);
|
||||
}
|
||||
CmdResult::AddRadialConstraint {
|
||||
circle,
|
||||
center,
|
||||
radius,
|
||||
location,
|
||||
diameter,
|
||||
name,
|
||||
expression,
|
||||
renamed,
|
||||
} => {
|
||||
use crate::modules::annotate::{diameter_dim, radius_dim};
|
||||
use crate::scene::named_parameters::{is_valid_name, DrivingValue};
|
||||
use crate::scene::parametric_constraints::{
|
||||
dynamic_dimension_text, ConstraintKind, ParametricRef,
|
||||
};
|
||||
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let kind = if diameter {
|
||||
ConstraintKind::Diameter
|
||||
} else {
|
||||
ConstraintKind::Radius
|
||||
};
|
||||
let refs = vec![circle];
|
||||
// A circle already carrying a radius or diameter constraint
|
||||
// is refused after the value, as the reference does.
|
||||
let duplicate = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.is_some_and(|set| {
|
||||
set.constraints.iter().any(|c| {
|
||||
c.enabled
|
||||
&& matches!(
|
||||
c.kind,
|
||||
ConstraintKind::Radius | ConstraintKind::Diameter
|
||||
)
|
||||
&& c.refs.iter().any(|r| r.entity == circle.entity)
|
||||
})
|
||||
});
|
||||
if duplicate {
|
||||
self.command_line
|
||||
.push_output("The constraint already exists on the selected objects.");
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
if let Err(message) = self.tabs[i].scene.validate_parametric_constraint(
|
||||
kind,
|
||||
&refs,
|
||||
Some(&DrivingValue::Literal(1.0)),
|
||||
) {
|
||||
self.reprompt_active_command(i, message);
|
||||
return Task::none();
|
||||
}
|
||||
let name = name.trim().to_string();
|
||||
if !is_valid_name(&name) {
|
||||
self.command_line.push_error(
|
||||
"Only alphanumeric names, starting with alpha characters, are allowed.",
|
||||
);
|
||||
self.reprompt_active_command(i, &format!("Invalid parameter name: {name}."));
|
||||
return Task::none();
|
||||
}
|
||||
if renamed && self.tabs[i].scene.named_parameters().contains(&name) {
|
||||
self.reprompt_active_command(i, "Parameter with this name already exists.");
|
||||
return Task::none();
|
||||
}
|
||||
let mut table = self.tabs[i].scene.named_parameters().clone();
|
||||
if let Err(error) = table.set(&name, &expression) {
|
||||
self.reprompt_active_command(i, &error.to_string());
|
||||
return Task::none();
|
||||
}
|
||||
// A circle has no zero or negative size: the value is
|
||||
// refused and the prompt comes back.
|
||||
let value = match table.resolve(&name) {
|
||||
Ok(value) if value.is_finite() && value > 0.0 => value,
|
||||
_ => {
|
||||
self.command_line.push_error("Invalid expression.");
|
||||
self.reprompt_active_command(
|
||||
i,
|
||||
"The parameter is used in an expression which results in an invalid value for a dimensional constraint.",
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let annotational = self.constraint_form_annotational;
|
||||
let text = dynamic_dimension_text(
|
||||
&name,
|
||||
value,
|
||||
self.tabs[i].scene.constraint_name_format,
|
||||
false,
|
||||
Some(&expression),
|
||||
annotational,
|
||||
None,
|
||||
);
|
||||
let entity = if diameter {
|
||||
diameter_dim::diameter_constraint_entity(center, radius, location, Some(text))
|
||||
} else {
|
||||
radius_dim::radius_constraint_entity(center, radius, location, Some(text))
|
||||
};
|
||||
let Some(mut entity) = entity else {
|
||||
self.reprompt_active_command(i, "No valid constraint point found.");
|
||||
return Task::none();
|
||||
};
|
||||
crate::scene::creation_style::apply_current_creation_styles(
|
||||
&self.tabs[i].scene.document,
|
||||
&mut entity,
|
||||
);
|
||||
if annotational {
|
||||
entity
|
||||
.as_entity_mut()
|
||||
.set_layer(self.tabs[i].active_layer.clone());
|
||||
} else {
|
||||
self.tabs[i].scene.ensure_dynamic_dimension_layer();
|
||||
entity.as_entity_mut().set_layer(
|
||||
crate::scene::parametric_constraints::DYNAMIC_DIMENSION_LAYER.to_string(),
|
||||
);
|
||||
entity.common_mut().color = acadrust::types::Color::Rgb {
|
||||
r: 103,
|
||||
g: 109,
|
||||
b: 118,
|
||||
};
|
||||
}
|
||||
let constraints_before = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
crate::scene::parametric_constraints::ParametricConstraintSet::new(scope)
|
||||
});
|
||||
// The centre stays where it is; the value moves the rim.
|
||||
let anchors = vec![ParametricRef::center(circle.entity)];
|
||||
let pending = self.begin_undo(
|
||||
i,
|
||||
if diameter {
|
||||
"Diameter constraint"
|
||||
} else {
|
||||
"Radius constraint"
|
||||
},
|
||||
2,
|
||||
false,
|
||||
);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||
self.tabs[i].scene.record_undo_named_parameters_before();
|
||||
self.tabs[i].scene.named_parameters = table;
|
||||
let dimension = self.tabs[i].scene.add_entity(entity);
|
||||
let set = self.tabs[i].scene.parametric_constraint_set_mut(scope);
|
||||
let id = set.add(kind, refs, Some(DrivingValue::Named(name)));
|
||||
set.dimensions.insert(id, dimension);
|
||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||
scope,
|
||||
id,
|
||||
self.constraint_bar_display,
|
||||
);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.attach_dimension_association(dimension, vec![Some(circle.entity)]);
|
||||
let changes = vec![(circle.entity, crate::scene::ChangeKind::Modified)];
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities_with_parametric_policy(&changes, &anchors, false);
|
||||
self.tabs[i].scene.refresh_hidden_dynamic_dimensions();
|
||||
self.tabs[i].scene.refresh_dynamic_dimension_scales(true);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.refresh_properties();
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddAngularConstraint {
|
||||
refs,
|
||||
points,
|
||||
@@ -4533,172 +4705,6 @@ impl OpenCADStudio {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::AddRadialConstraint {
|
||||
circle,
|
||||
location,
|
||||
name,
|
||||
expression,
|
||||
renamed,
|
||||
} => {
|
||||
use crate::scene::named_parameters::{is_valid_name, DrivingValue};
|
||||
use crate::scene::parametric_constraints::{dynamic_dimension_text, ConstraintKind};
|
||||
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let kind = ConstraintKind::Radius;
|
||||
let refs = vec![circle];
|
||||
// A second radius on the same circle is refused after the
|
||||
// value, and the command ends.
|
||||
let duplicate = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.is_some_and(|set| {
|
||||
set.constraints
|
||||
.iter()
|
||||
.any(|c| c.enabled && c.kind == kind && c.refs == refs)
|
||||
});
|
||||
if duplicate {
|
||||
self.command_line
|
||||
.push_output("The constraint already exists on the selected objects.");
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
if let Err(message) = self.tabs[i].scene.validate_parametric_constraint(
|
||||
kind,
|
||||
&refs,
|
||||
Some(&DrivingValue::Literal(1.0)),
|
||||
) {
|
||||
self.reprompt_active_command(i, message);
|
||||
return Task::none();
|
||||
}
|
||||
let name = name.trim().to_string();
|
||||
if !is_valid_name(&name) {
|
||||
self.command_line.push_error(
|
||||
"Only alphanumeric names, starting with alpha characters, are allowed.",
|
||||
);
|
||||
self.reprompt_active_command(i, &format!("Invalid parameter name: {name}."));
|
||||
return Task::none();
|
||||
}
|
||||
if renamed && self.tabs[i].scene.named_parameters().contains(&name) {
|
||||
self.reprompt_active_command(i, "Parameter with this name already exists.");
|
||||
return Task::none();
|
||||
}
|
||||
let mut table = self.tabs[i].scene.named_parameters().clone();
|
||||
if let Err(error) = table.set(&name, &expression) {
|
||||
self.reprompt_active_command(i, &error.to_string());
|
||||
return Task::none();
|
||||
}
|
||||
// A negative radius is its magnitude (`rad1=-20` is a radius
|
||||
// of 20); a zero radius has no circle.
|
||||
let value = match table.resolve(&name) {
|
||||
Ok(value) if value.is_finite() && value != 0.0 => value,
|
||||
_ => {
|
||||
self.command_line.push_error("Invalid expression.");
|
||||
self.reprompt_active_command(
|
||||
i,
|
||||
"The parameter is used in an expression which results in an invalid value for a dimensional constraint.",
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let annotational = self.constraint_form_annotational;
|
||||
let text = dynamic_dimension_text(
|
||||
&name,
|
||||
value,
|
||||
self.tabs[i].scene.constraint_name_format,
|
||||
false,
|
||||
Some(&expression),
|
||||
annotational,
|
||||
None,
|
||||
);
|
||||
let source = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(circle.entity)
|
||||
.and_then(|entity| {
|
||||
crate::scene::dimension_assoc::radial_source_at(
|
||||
entity,
|
||||
acadrust::types::Vector3::new(location.x, location.y, location.z),
|
||||
)
|
||||
});
|
||||
let Some(source) = source else {
|
||||
self.reprompt_active_command(
|
||||
i,
|
||||
crate::modules::parametric::DimConstraintCommand::NO_OBJECT,
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
let mut entity = crate::modules::annotate::radius_dim::radial_dimension_entity(
|
||||
source,
|
||||
location,
|
||||
Some(text),
|
||||
None,
|
||||
);
|
||||
crate::scene::creation_style::apply_current_creation_styles(
|
||||
&self.tabs[i].scene.document,
|
||||
&mut entity,
|
||||
);
|
||||
if annotational {
|
||||
entity
|
||||
.as_entity_mut()
|
||||
.set_layer(self.tabs[i].active_layer.clone());
|
||||
} else {
|
||||
self.tabs[i].scene.ensure_dynamic_dimension_layer();
|
||||
entity.as_entity_mut().set_layer(
|
||||
crate::scene::parametric_constraints::DYNAMIC_DIMENSION_LAYER.to_string(),
|
||||
);
|
||||
entity.common_mut().color = acadrust::types::Color::Rgb {
|
||||
r: 103,
|
||||
g: 109,
|
||||
b: 118,
|
||||
};
|
||||
}
|
||||
let constraints_before = self.tabs[i]
|
||||
.scene
|
||||
.parametric_constraint_set(scope)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
crate::scene::parametric_constraints::ParametricConstraintSet::new(scope)
|
||||
});
|
||||
// The center stays; a value other than the measured radius
|
||||
// resizes the circle about it.
|
||||
let anchors = crate::scene::parametric_constraints::dimensional_anchor_refs(
|
||||
&self.tabs[i].scene.document,
|
||||
&refs,
|
||||
);
|
||||
let pending = self.begin_undo(i, "Radius constraint", 2, false);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||
self.tabs[i].scene.record_undo_named_parameters_before();
|
||||
self.tabs[i].scene.named_parameters = table;
|
||||
let dimension = self.tabs[i].scene.add_entity(entity);
|
||||
let set = self.tabs[i].scene.parametric_constraint_set_mut(scope);
|
||||
let id = set.add(kind, refs, Some(DrivingValue::Named(name)));
|
||||
set.dimensions.insert(id, dimension);
|
||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||
scope,
|
||||
id,
|
||||
self.constraint_bar_display,
|
||||
);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.attach_dimension_association(dimension, vec![Some(circle.entity)]);
|
||||
self.tabs[i].scene.bump_entities_with_parametric_policy(
|
||||
&[(circle.entity, crate::scene::ChangeKind::Modified)],
|
||||
&anchors,
|
||||
false,
|
||||
);
|
||||
self.tabs[i].scene.refresh_hidden_dynamic_dimensions();
|
||||
self.tabs[i].scene.refresh_dynamic_dimension_scales(true);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.refresh_properties();
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::MakeParallel {
|
||||
first_line,
|
||||
first_ends,
|
||||
|
||||
+20
-12
@@ -1891,22 +1891,32 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
|
||||
"DCRADIUS" => {
|
||||
"DCRADIUS" | "DCDIAMETER" => {
|
||||
use crate::modules::parametric::{DimConstraintAxis, DimConstraintCommand};
|
||||
use crate::scene::parametric_constraints::next_radial_parameter_name;
|
||||
// Run on its own it leaves DIMCONSTRAINT's default alone.
|
||||
let name = next_radial_parameter_name(self.tabs[i].scene.named_parameters());
|
||||
// The circle or arc is picked inside the command.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
let command = DimConstraintCommand::new(DimConstraintAxis::Radius, name);
|
||||
let diameter = cmd == "DCDIAMETER";
|
||||
// Run on its own either one leaves DIMCONSTRAINT's default
|
||||
// alone; the Dispatch arm sets it when DIMCONSTRAINT is what
|
||||
// asked for this command.
|
||||
// The reference asks for the circle itself, whatever is
|
||||
// selected, so a pick-first set is left alone.
|
||||
let name = next_radial_parameter_name(
|
||||
self.tabs[i].scene.named_parameters(),
|
||||
diameter,
|
||||
);
|
||||
let command = DimConstraintCommand::new(
|
||||
if diameter {
|
||||
DimConstraintAxis::Diameter
|
||||
} else {
|
||||
DimConstraintAxis::Radius
|
||||
},
|
||||
name,
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
|
||||
"DCONSTRAINT" | "DCDIAMETER" => {
|
||||
if cmd == "DCDIAMETER" {
|
||||
self.dim_constraint_last = "Diameter";
|
||||
}
|
||||
"DCONSTRAINT" => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
@@ -1924,8 +1934,6 @@ impl OpenCADStudio {
|
||||
"DCHORIZONTAL" => ("DCHORIZONTAL", DistanceMode::X),
|
||||
"DCVERTICAL" => ("DCVERTICAL", DistanceMode::Y),
|
||||
"DCALIGNED" => ("DCALIGNED", DistanceMode::Aligned),
|
||||
"DCRADIUS" => ("DCRADIUS", DistanceMode::Radius),
|
||||
"DCDIAMETER" => ("DCDIAMETER", DistanceMode::Diameter),
|
||||
_ => ("DCONSTRAINT", DistanceMode::Auto),
|
||||
};
|
||||
match DistanceConstraintCommand::with_mode(
|
||||
|
||||
+9
-13
@@ -2369,7 +2369,10 @@ impl OpenCADStudio {
|
||||
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_),
|
||||
) => t!("Angular Dimensional Constraint"),
|
||||
acadrust::EntityType::Dimension(Dimension::Radius(_)) => {
|
||||
t!("Radial Dimension (Dynamic)")
|
||||
t!("Radius Dimensional Constraint")
|
||||
}
|
||||
acadrust::EntityType::Dimension(Dimension::Diameter(_)) => {
|
||||
t!("Diameter Dimensional Constraint")
|
||||
}
|
||||
_ => t!("Linear Dimensional Constraint"),
|
||||
}
|
||||
@@ -2853,13 +2856,6 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if matches!(
|
||||
contextual.as_ref(),
|
||||
acadrust::EntityType::Dimension(acadrust::entities::Dimension::Radius(_))
|
||||
) && self.tabs[i].scene.is_dynamic_dimension(handle)
|
||||
{
|
||||
// A dynamic radius shows only its text square.
|
||||
entity_grips.retain(|grip| grip.id == 2);
|
||||
} else if self.tabs[i].scene.is_dynamic_dimension(handle) {
|
||||
// A dynamic dimension shows the reference's grips: a
|
||||
// triangle at each constraint point aimed at the other
|
||||
@@ -3712,6 +3708,11 @@ fn set_row(sections: &mut [crate::scene::model::object::PropSection], field: &st
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace a row's value with an arbitrary control (editable field, dropdown,
|
||||
/// colour picker …) rather than plain read-only text.
|
||||
/// A dimensional constraint's Properties as the reference shows them: the
|
||||
/// Constraint rows, then only the text rotation for a dynamic dimension
|
||||
/// and the full dimension sections for an annotational one.
|
||||
/// A Yes/No list row, as the reference shows on/off object properties.
|
||||
fn yes_no_choice(flag: bool) -> crate::scene::model::object::PropValue {
|
||||
crate::scene::model::object::PropValue::Choice {
|
||||
@@ -3720,9 +3721,6 @@ fn yes_no_choice(flag: bool) -> crate::scene::model::object::PropValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// A dimensional constraint's Properties as the reference shows them: the
|
||||
/// Constraint rows, then only the text rotation for a dynamic dimension
|
||||
/// and the full dimension sections for an annotational one.
|
||||
fn dynamic_dimension_sections(
|
||||
scene: &crate::scene::Scene,
|
||||
handle: Handle,
|
||||
@@ -3858,8 +3856,6 @@ fn dynamic_dimension_sections(
|
||||
result
|
||||
}
|
||||
|
||||
/// Replace a row's value with an arbitrary control (editable field, dropdown,
|
||||
/// colour picker …) rather than plain read-only text.
|
||||
fn set_row_value(
|
||||
sections: &mut [crate::scene::model::object::PropSection],
|
||||
field: &str,
|
||||
|
||||
+16
-9
@@ -1635,6 +1635,22 @@ pub enum CmdResult {
|
||||
renamed: bool,
|
||||
label: &'static str,
|
||||
},
|
||||
/// Adds a radius or diameter constraint with its dynamic radial
|
||||
/// dimension and the `radN`/`diaN` parameter that drives it.
|
||||
AddRadialConstraint {
|
||||
circle: crate::scene::parametric_constraints::ParametricRef,
|
||||
center: DVec3,
|
||||
radius: f64,
|
||||
/// Where the dimension line was picked; its direction from the
|
||||
/// centre places the dimension.
|
||||
location: DVec3,
|
||||
diameter: bool,
|
||||
name: String,
|
||||
expression: String,
|
||||
/// The user named the parameter (`name=expression`); an existing
|
||||
/// name is then refused.
|
||||
renamed: bool,
|
||||
},
|
||||
/// Adds an angular constraint with its dynamic angular dimension and
|
||||
/// the `angN` parameter that drives it. Two lines: `refs` =
|
||||
/// `[first_line, second_line]`, `points` = both lines' ends; three
|
||||
@@ -1650,15 +1666,6 @@ pub enum CmdResult {
|
||||
expression: String,
|
||||
renamed: bool,
|
||||
},
|
||||
/// Adds a radius constraint on a circle or arc with its dynamic radial
|
||||
/// dimension (text at `location`) and the `radN` parameter that drives it.
|
||||
AddRadialConstraint {
|
||||
circle: crate::scene::parametric_constraints::ParametricRef,
|
||||
location: DVec3,
|
||||
name: String,
|
||||
expression: String,
|
||||
renamed: bool,
|
||||
},
|
||||
/// Aligned's 2Lines: makes `second_line` parallel to `first_line` (whose
|
||||
/// ends stay put), then hands the second line's solved ends back through
|
||||
/// `CadCommand::accept_parallel_line`.
|
||||
|
||||
+155
-56
@@ -1134,11 +1134,7 @@ impl Grippable for Dimension {
|
||||
// of the world origin, so it stays on the visible text and grabbable.
|
||||
let text = {
|
||||
let p = self.base().text_middle_point;
|
||||
// A dynamic radius constraint draws its text at mid-radius, not
|
||||
// at the stored point; its grip sits on the drawn text.
|
||||
let drawn_elsewhere =
|
||||
matches!(self, Dimension::Radius(_)) && dynamic_constraint_dimension(self);
|
||||
if p.x * p.x + p.y * p.y + p.z * p.z > 1e-16 && !drawn_elsewhere {
|
||||
if p.x * p.x + p.y * p.y + p.z * p.z > 1e-16 {
|
||||
dv3(&p)
|
||||
} else {
|
||||
dv3(&dimension_text_pos_f64(self, None, 2.5, 1.0))
|
||||
@@ -3864,6 +3860,8 @@ fn tessellate_dimension_inner(
|
||||
ticks: dimtsz_raw > 1e-9,
|
||||
arrow_len: dimasz,
|
||||
text_width: text_layout.width,
|
||||
text_height: dim_txt as f32,
|
||||
constraint: dynamic_constraint_dimension(dim),
|
||||
dimatfit: style.map(|s| s.dimatfit).unwrap_or(3),
|
||||
dimtix: style.is_some_and(|s| s.dimtix),
|
||||
dimtofl: style.map(|s| s.dimtofl).unwrap_or(false),
|
||||
@@ -4695,6 +4693,11 @@ struct DimLineParams {
|
||||
/// Arrowhead length (DIMASZ, scaled) — used to decide arrow-outside fit.
|
||||
arrow_len: f32,
|
||||
text_width: f32,
|
||||
/// Text height (DIMTXT, scaled) — a radial leader is sized by it.
|
||||
text_height: f32,
|
||||
/// This dimension is a dimensional constraint's dynamic dimension, which
|
||||
/// the reference draws by its own radial rules.
|
||||
constraint: bool,
|
||||
dimatfit: i16,
|
||||
dimtix: bool,
|
||||
dimtofl: bool,
|
||||
@@ -4780,27 +4783,46 @@ fn dimension_geometry(
|
||||
}
|
||||
let radius = (point - center).length();
|
||||
let text_is_outside = text.distance(center) > radius + 1e-5;
|
||||
// Text inside the arc: the dimension line runs from the arc point
|
||||
// to the centre. Text outside: only a leader from the arc point to
|
||||
// the text, unless DIMTOFL asks for the inside line as well.
|
||||
if !jogged && !suppress.dim2 && (!text_is_outside || params.dimtofl) {
|
||||
if dynamic_constraint_dimension(dim) {
|
||||
// The line passes behind the mid-radius text.
|
||||
if params.constraint {
|
||||
// A dimensional constraint reads on its own dimension line,
|
||||
// which runs from the centre to the arc point and breaks
|
||||
// where the text sits on it. Text that no longer fits inside
|
||||
// is reached by a leader from the arc point, and the
|
||||
// arrowhead stays on the arc either way.
|
||||
if !jogged && !suppress.dim2 {
|
||||
add_segment_with_text_break(&mut g.dim_lines, center, point, params.text_break);
|
||||
} else {
|
||||
}
|
||||
if text_is_outside && !suppress.dim2 {
|
||||
append_radial_leader(&mut g, point, point - center, text, ¶ms);
|
||||
}
|
||||
if !suppress.dim2 {
|
||||
// The arrowhead's tip is on the arc, its body inside.
|
||||
append_arrow(&mut g, point, normalized_or(center - point, Vec3::X), arrow1);
|
||||
}
|
||||
// The centre mark belongs to a radius drawn without its
|
||||
// inside line; the line itself already marks the centre.
|
||||
if jogged || suppress.dim2 {
|
||||
append_center_mark(&mut g, center, params.dimcen, radius);
|
||||
}
|
||||
} else {
|
||||
// Text inside the arc: the dimension line runs from the arc
|
||||
// point to the centre. Text outside: only a leader from the
|
||||
// arc point to the text, unless DIMTOFL asks for the inside
|
||||
// line as well.
|
||||
if !jogged && !suppress.dim2 && (!text_is_outside || params.dimtofl) {
|
||||
add_segment(&mut g.dim_lines, center, point);
|
||||
}
|
||||
}
|
||||
if text_is_outside && !suppress.dim2 {
|
||||
append_radial_leader(&mut g, point, text, ¶ms);
|
||||
}
|
||||
if !suppress.dim2 {
|
||||
// The arrowhead sits on the arc with its body toward the text.
|
||||
let body = if text_is_outside { point - center } else { center - point };
|
||||
append_arrow(&mut g, point, normalized_or(body, Vec3::X), arrow1);
|
||||
}
|
||||
if text_is_outside {
|
||||
append_center_mark(&mut g, center, params.dimcen, radius);
|
||||
if text_is_outside && !suppress.dim2 {
|
||||
append_radial_leader(&mut g, point, point - center, text, ¶ms);
|
||||
}
|
||||
if !suppress.dim2 {
|
||||
// The arrowhead sits on the arc with its body toward the text.
|
||||
let body = if text_is_outside { point - center } else { center - point };
|
||||
append_arrow(&mut g, point, normalized_or(body, Vec3::X), arrow1);
|
||||
}
|
||||
if text_is_outside {
|
||||
append_center_mark(&mut g, center, params.dimcen, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
Dimension::Diameter(d) => {
|
||||
@@ -4816,9 +4838,11 @@ fn dimension_geometry(
|
||||
params,
|
||||
suppress,
|
||||
);
|
||||
let center = (chord + far_chord) * 0.5;
|
||||
let radius = chord.distance(far_chord) * 0.5;
|
||||
append_center_mark(&mut g, center, params.dimcen, radius);
|
||||
if !params.constraint {
|
||||
let center = (chord + far_chord) * 0.5;
|
||||
let radius = chord.distance(far_chord) * 0.5;
|
||||
append_center_mark(&mut g, center, params.dimcen, radius);
|
||||
}
|
||||
}
|
||||
Dimension::Angular2Ln(d) => {
|
||||
// A two-line angular dimension stores two LINES, not two rays:
|
||||
@@ -5287,11 +5311,36 @@ fn append_linear_dimension(
|
||||
}
|
||||
}
|
||||
|
||||
/// Leader from a point on the circle to text outside it. Horizontal text gets a
|
||||
/// hook one arrow long that the text sits against; aligned text is reached by a
|
||||
/// straight leader that stops a gap short of it. `text_width` already carries
|
||||
/// a gap on each side.
|
||||
fn append_radial_leader(g: &mut DimGeom, tip: Vec3, text: Vec3, params: &DimLineParams) {
|
||||
/// Leader from a point on the circle to text outside it.
|
||||
///
|
||||
/// A dimensional constraint's dimension leaves the rim along its dimension
|
||||
/// line, elbows level with the text and hooks under it. An ordinary radial
|
||||
/// dimension keeps its measured layout: horizontal text gets a hook one arrow
|
||||
/// long that the text sits against, and aligned text is reached by a straight
|
||||
/// leader that stops a gap short of it. `text_width` already carries a gap on
|
||||
/// each side.
|
||||
fn append_radial_leader(
|
||||
g: &mut DimGeom,
|
||||
tip: Vec3,
|
||||
direction: Vec3,
|
||||
text: Vec3,
|
||||
params: &DimLineParams,
|
||||
) {
|
||||
if params.constraint {
|
||||
let side = if text.x >= tip.x { 1.0 } else { -1.0 };
|
||||
let edge = match params.leader_anchor {
|
||||
Some(anchor) => anchor,
|
||||
None => Vec3::new(text.x - side * params.text_width * 0.5, text.y, text.z),
|
||||
};
|
||||
let elbow = tip
|
||||
+ normalized_or(direction, Vec3::X)
|
||||
* radial_leader_run(params.text_height as f64) as f32;
|
||||
// The hook is horizontal at the text's height; the run reaches it.
|
||||
let elbow = Vec3::new(elbow.x, edge.y, edge.z);
|
||||
add_segment(&mut g.dim_lines, tip, elbow);
|
||||
add_segment(&mut g.dim_lines, elbow, edge);
|
||||
return;
|
||||
}
|
||||
if params.horizontal_text {
|
||||
let side = if text.x >= tip.x { 1.0 } else { -1.0 };
|
||||
let (hook_start, text_edge) = match params.leader_anchor {
|
||||
@@ -5333,11 +5382,13 @@ fn append_diameter_dimension(
|
||||
}
|
||||
let center = (chord + far_chord) * 0.5;
|
||||
let text_is_outside = params.text_position.distance(center) > diameter * 0.5 + 1e-5;
|
||||
if text_is_outside && !params.dimtofl {
|
||||
// Outside text without DIMTOFL: no line across the circle, just a
|
||||
// leader from the near side with the arrowhead on the circle and its
|
||||
// body toward the text. DIMTMOVE 2 keeps the arrowhead and drops the
|
||||
// leader.
|
||||
if text_is_outside && (params.constraint || !params.dimtofl) {
|
||||
// A dimensional constraint carries its text on a leader from the
|
||||
// nearer end of the dimension line, and the line and both arrowheads
|
||||
// stay inside the circle. An ordinary diameter draws no line across
|
||||
// the circle at all: just that leader, with one arrowhead on the
|
||||
// circle and its body toward the text. DIMTMOVE 2 keeps the arrowhead
|
||||
// and drops the leader.
|
||||
let (tip, suppressed) = if params.text_position.distance_squared(chord)
|
||||
<= params.text_position.distance_squared(far_chord)
|
||||
{
|
||||
@@ -5345,13 +5396,15 @@ fn append_diameter_dimension(
|
||||
} else {
|
||||
(far_chord, suppress.dim2)
|
||||
};
|
||||
if !suppressed {
|
||||
if params.text_movement != 2 {
|
||||
append_radial_leader(g, tip, params.text_position, ¶ms);
|
||||
if !suppressed && params.text_movement != 2 {
|
||||
append_radial_leader(g, tip, tip - center, params.text_position, ¶ms);
|
||||
}
|
||||
if !params.constraint {
|
||||
if !suppressed {
|
||||
append_arrow(g, tip, normalized_or(tip - center, axis), arrow1);
|
||||
}
|
||||
append_arrow(g, tip, normalized_or(tip - center, axis), arrow1);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Text projected outside the diameter requires inward-pointing arrowheads.
|
||||
let text_along = (params.text_position - chord).dot(axis);
|
||||
@@ -5359,6 +5412,9 @@ fn append_diameter_dimension(
|
||||
|
||||
let arrows_outside = if params.ticks || params.arrow_len <= 1e-6 {
|
||||
false
|
||||
} else if text_is_outside {
|
||||
// The leader carries the text; the arrowheads keep the circle.
|
||||
false
|
||||
} else if text_outside {
|
||||
true
|
||||
} else if diameter < 2.0 * params.arrow_len {
|
||||
@@ -5401,6 +5457,10 @@ fn append_diameter_dimension(
|
||||
}
|
||||
}
|
||||
|
||||
if params.constraint && !draw_inside_line {
|
||||
let center = (chord + far_chord) * 0.5;
|
||||
append_center_mark(g, center, params.dimcen, diameter * 0.5);
|
||||
}
|
||||
if arrows_outside {
|
||||
append_arrow(g, chord, -axis, arrow1);
|
||||
append_arrow(g, far_chord, axis, arrow2);
|
||||
@@ -7551,15 +7611,6 @@ fn stored_text_point(dim: &Dimension) -> Option<Vector3> {
|
||||
(p.x * p.x + p.y * p.y + p.z * p.z > 1e-16).then_some(p)
|
||||
}
|
||||
|
||||
/// A dimensional constraint's dynamic dimension: the reference keeps it on
|
||||
/// its own hidden layer and draws it by its own rules.
|
||||
fn dynamic_constraint_dimension(dim: &Dimension) -> bool {
|
||||
dim.base()
|
||||
.common
|
||||
.layer
|
||||
.eq_ignore_ascii_case(crate::scene::parametric_constraints::DYNAMIC_DIMENSION_LAYER)
|
||||
}
|
||||
|
||||
/// The point on the circle a radial leader leaves from: the arc point of a
|
||||
/// radius, or whichever end of a diameter's chord is nearer the text.
|
||||
fn radial_leader_tip(dim: &Dimension, text: Vector3) -> Vector3 {
|
||||
@@ -7576,6 +7627,42 @@ fn radial_leader_tip(dim: &Dimension, text: Vector3) -> Vector3 {
|
||||
}
|
||||
}
|
||||
|
||||
/// A dimensional constraint's dynamic dimension: the reference keeps it on
|
||||
/// its own hidden layer and draws it by its own rules.
|
||||
fn dynamic_constraint_dimension(dim: &Dimension) -> bool {
|
||||
dim.base()
|
||||
.common
|
||||
.layer
|
||||
.eq_ignore_ascii_case(crate::scene::parametric_constraints::DYNAMIC_DIMENSION_LAYER)
|
||||
}
|
||||
|
||||
/// How far a radial leader runs out from the rim before its hook, and how
|
||||
/// long that hook is: about one text height, so the text always clears the
|
||||
/// circle.
|
||||
fn radial_leader_run(text_height: f64) -> f64 {
|
||||
text_height
|
||||
}
|
||||
|
||||
/// Where a radius or diameter dimension's text sits once it no longer fits
|
||||
/// inside: beside the circle, level with the end of the leader's run, its
|
||||
/// near edge one arrow away so the hook has room.
|
||||
fn radial_outside_text(
|
||||
tip: Vector3,
|
||||
ux: f64,
|
||||
uy: f64,
|
||||
text_height: f64,
|
||||
text_width: f64,
|
||||
) -> Vector3 {
|
||||
let run = radial_leader_run(text_height);
|
||||
let elbow = Vector3::new(tip.x + ux * run, tip.y + uy * run, tip.z);
|
||||
let side = if ux >= 0.0 { 1.0 } else { -1.0 };
|
||||
Vector3::new(
|
||||
elbow.x + side * (run + text_width * 0.5),
|
||||
elbow.y,
|
||||
elbow.z,
|
||||
)
|
||||
}
|
||||
|
||||
fn dimension_text_pos_f64(
|
||||
dim: &Dimension,
|
||||
style: Option<&DimStyle>,
|
||||
@@ -7616,12 +7703,6 @@ fn dimension_text_pos_f64(
|
||||
|
||||
if let Some(point) = stored_text_point(dim) {
|
||||
return match dim {
|
||||
// A dynamic radius constraint draws its text at mid-radius on the
|
||||
// dimension line, whatever text position the file stores (the
|
||||
// picked location), as the reference does.
|
||||
Dimension::Radius(d) if dynamic_constraint_dimension(dim) => {
|
||||
(d.angle_vertex + d.definition_point) * 0.5
|
||||
}
|
||||
// DIMTMOVE 1 stores where the leader's hook starts: one arrow of
|
||||
// hook, a gap, then the text, all running away from the arc.
|
||||
// Measured on 39 such dimensions the stored point sits 2.4 text
|
||||
@@ -7730,6 +7811,11 @@ fn dimension_text_pos_f64(
|
||||
let ux = dx / radius;
|
||||
let uy = dy / radius;
|
||||
let outside = dimension_text_is_outside(dim, style);
|
||||
if outside && dynamic_constraint_dimension(dim) {
|
||||
// A constraint's text moves beside the circle at the end of
|
||||
// its leader; an ordinary radius keeps its measured offset.
|
||||
return radial_outside_text(d.definition_point, ux, uy, text_height, text_w);
|
||||
}
|
||||
let (mut x, mut y) = if outside {
|
||||
let distance = arrow + text_w * 0.5 + dimgap;
|
||||
(
|
||||
@@ -7771,6 +7857,16 @@ fn dimension_text_pos_f64(
|
||||
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);
|
||||
if dimension_text_is_outside(dim, style) && dynamic_constraint_dimension(dim) {
|
||||
// Beyond the end the dimension line was placed towards.
|
||||
return radial_outside_text(
|
||||
d.angle_vertex,
|
||||
-dx / len,
|
||||
-dy / len,
|
||||
text_height,
|
||||
text_w,
|
||||
);
|
||||
}
|
||||
text_on_dim_line(
|
||||
d.angle_vertex,
|
||||
d.definition_point,
|
||||
@@ -7878,6 +7974,9 @@ pub(crate) fn baked_large_radial_geometry(
|
||||
ticks: tick_size > 1.0e-9,
|
||||
arrow_len: arrow_size,
|
||||
text_width: text.width,
|
||||
text_height: text_height as f32,
|
||||
// A LargeRadial is never a dimensional constraint's dimension.
|
||||
constraint: false,
|
||||
dimatfit: style.map(|style| style.dimatfit).unwrap_or(3),
|
||||
dimtix: style.is_some_and(|style| style.dimtix),
|
||||
dimtofl: style.is_some_and(|style| style.dimtofl),
|
||||
|
||||
@@ -87,6 +87,25 @@ impl DiameterDimensionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// A diameter dimension for a dimensional constraint: the dimension line
|
||||
/// runs through the centre towards `location`, and the style places the
|
||||
/// text.
|
||||
pub(crate) fn diameter_constraint_entity(
|
||||
center: DVec3,
|
||||
radius: f64,
|
||||
location: DVec3,
|
||||
text: Option<String>,
|
||||
) -> Option<EntityType> {
|
||||
let direction = super::radius_dim::radial_direction(center, location)?;
|
||||
let chord = center + direction * radius;
|
||||
let far_chord = center - direction * radius;
|
||||
let mut dim = DimensionDiameter::new(v3(chord), v3(far_chord));
|
||||
dim.leader_length = chord.distance(location).max(radius * 0.5);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text);
|
||||
Some(EntityType::Dimension(Dimension::Diameter(dim)))
|
||||
}
|
||||
|
||||
impl CadCommand for DiameterDimensionCommand {
|
||||
fn set_working_plane(&mut self, _plane: WorkingPlane) {
|
||||
}
|
||||
|
||||
@@ -96,6 +96,31 @@ pub(crate) fn radial_dimension_entity(
|
||||
plane.place_entity(EntityType::Dimension(Dimension::Radius(dim)))
|
||||
}
|
||||
|
||||
/// A radius dimension for a dimensional constraint: the leader leaves the
|
||||
/// centre towards `location`, and the style places the text.
|
||||
pub(crate) fn radius_constraint_entity(
|
||||
center: DVec3,
|
||||
radius: f64,
|
||||
location: DVec3,
|
||||
text: Option<String>,
|
||||
) -> Option<EntityType> {
|
||||
let direction = radial_direction(center, location)?;
|
||||
let chord = center + direction * radius;
|
||||
let mut dim = DimensionRadius::new(v3(center), v3(chord));
|
||||
dim.base.definition_point = v3(chord);
|
||||
dim.leader_length = chord.distance(location).max(radius * 0.5);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text);
|
||||
Some(EntityType::Dimension(Dimension::Radius(dim)))
|
||||
}
|
||||
|
||||
/// The unit direction a radial constraint's dimension points in, from the
|
||||
/// centre to where its dimension line was picked.
|
||||
pub(crate) fn radial_direction(center: DVec3, location: DVec3) -> Option<DVec3> {
|
||||
let delta = location - center;
|
||||
(delta.length() > 1.0e-9).then(|| delta.normalize())
|
||||
}
|
||||
|
||||
impl CadCommand for RadiusDimensionCommand {
|
||||
fn set_working_plane(&mut self, _plane: WorkingPlane) {
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ pub enum DimConstraintAxis {
|
||||
Aligned,
|
||||
/// The angle between two lines, or at a vertex between two points.
|
||||
Angular,
|
||||
/// The radius of a circle or arc.
|
||||
/// A circle's or arc's radius.
|
||||
Radius,
|
||||
/// A circle's or arc's diameter.
|
||||
Diameter,
|
||||
}
|
||||
|
||||
/// A picked line (a line entity or one polyline segment) an Aligned
|
||||
@@ -108,15 +110,15 @@ enum Step {
|
||||
sector: u8,
|
||||
measured: f64,
|
||||
},
|
||||
/// Radius: `Select arc or circle:`
|
||||
RadialSelect,
|
||||
/// `Specify dimension line location:` of a radius.
|
||||
RadialLocation { circle: ParametricRef, radius: f64 },
|
||||
/// `Enter value or name and value <rad1=30>:`
|
||||
/// Radius/Diameter: `Select arc or circle:`
|
||||
RadialObject,
|
||||
/// `Specify dimension line location:` of a radius or diameter.
|
||||
RadialLocation { target: RadialTarget },
|
||||
/// `Enter value or name and value <dia1=50>:`
|
||||
RadialValue {
|
||||
circle: ParametricRef,
|
||||
radius: f64,
|
||||
target: RadialTarget,
|
||||
location: DVec3,
|
||||
measured: f64,
|
||||
},
|
||||
/// `Enter value or name and value <d1=100>:`
|
||||
Value {
|
||||
@@ -147,6 +149,14 @@ enum AngularData {
|
||||
},
|
||||
}
|
||||
|
||||
/// The circle or arc a Radius/Diameter constraint measures.
|
||||
#[derive(Clone, Copy)]
|
||||
struct RadialTarget {
|
||||
circle: ParametricRef,
|
||||
center: DVec3,
|
||||
radius: f64,
|
||||
}
|
||||
|
||||
/// The reference's Linear/Horizontal/Vertical/Aligned dimensional constraint:
|
||||
/// two constraint points (or one object's ends), a dimension line location,
|
||||
/// then the parameter name and expression the dynamic dimension carries.
|
||||
@@ -167,15 +177,13 @@ impl DimConstraintCommand {
|
||||
"The object or point is already selected. Select a different object or constraint point.";
|
||||
pub const INVALID_LINE: &'static str = "Invalid selection for Aligned. Select a line segment, polyline segment, text, MText, major or minor axis of ellipse or elliptical arc.";
|
||||
pub const PARALLEL_LINES: &'static str = "Lines are parallel.";
|
||||
pub const INVALID_RADIAL: &'static str =
|
||||
"Invalid selection for Radial. Select a circle or arc.";
|
||||
|
||||
pub fn new(axis: DimConstraintAxis, default_name: String) -> Self {
|
||||
Self {
|
||||
axis,
|
||||
step: match axis {
|
||||
DimConstraintAxis::Angular => Step::AngularFirst,
|
||||
DimConstraintAxis::Radius => Step::RadialSelect,
|
||||
DimConstraintAxis::Radius | DimConstraintAxis::Diameter => Step::RadialObject,
|
||||
_ => Step::First,
|
||||
},
|
||||
picked_entity: None,
|
||||
@@ -318,25 +326,6 @@ impl DimConstraintCommand {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_radial(&self, name: String, expression: String) -> Option<CmdResult> {
|
||||
let Step::RadialValue {
|
||||
circle, location, ..
|
||||
} = self.step
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if expression.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(CmdResult::AddRadialConstraint {
|
||||
circle,
|
||||
location,
|
||||
renamed: name != self.default_name,
|
||||
name,
|
||||
expression,
|
||||
})
|
||||
}
|
||||
|
||||
fn command_name(&self) -> &'static str {
|
||||
match self.axis {
|
||||
DimConstraintAxis::Linear => "DCLINEAR",
|
||||
@@ -345,6 +334,7 @@ impl DimConstraintCommand {
|
||||
DimConstraintAxis::Aligned => "DCALIGNED",
|
||||
DimConstraintAxis::Angular => "DCANGULAR",
|
||||
DimConstraintAxis::Radius => "DCRADIUS",
|
||||
DimConstraintAxis::Diameter => "DCDIAMETER",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,10 +345,47 @@ impl DimConstraintCommand {
|
||||
DimConstraintAxis::Vertical => "Vertical",
|
||||
DimConstraintAxis::Aligned => "Aligned",
|
||||
DimConstraintAxis::Angular => "Angular",
|
||||
DimConstraintAxis::Radius => "Radial",
|
||||
DimConstraintAxis::Radius => "Radius",
|
||||
DimConstraintAxis::Diameter => "Diameter",
|
||||
}
|
||||
}
|
||||
|
||||
/// The circle or arc a radial pick landed on, with its centre and radius.
|
||||
fn radial_target(entity: &EntityType, handle: Handle) -> Option<RadialTarget> {
|
||||
let (center, radius) = match entity {
|
||||
EntityType::Circle(circle) => (circle.center, circle.radius),
|
||||
EntityType::Arc(arc) => (arc.center, arc.radius),
|
||||
_ => return None,
|
||||
};
|
||||
(radius > 1.0e-12).then_some(RadialTarget {
|
||||
circle: ParametricRef::whole(handle),
|
||||
center: DVec3::new(center.x, center.y, center.z),
|
||||
radius,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_radial(&self, name: String, expression: String) -> Option<CmdResult> {
|
||||
let Step::RadialValue {
|
||||
target, location, ..
|
||||
} = self.step
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if expression.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(CmdResult::AddRadialConstraint {
|
||||
circle: target.circle,
|
||||
center: target.center,
|
||||
radius: target.radius,
|
||||
location,
|
||||
diameter: self.axis == DimConstraintAxis::Diameter,
|
||||
renamed: name != self.default_name,
|
||||
name,
|
||||
expression,
|
||||
})
|
||||
}
|
||||
|
||||
fn report(message: &str) -> CmdResult {
|
||||
CmdResult::ReportError(message.to_string())
|
||||
}
|
||||
@@ -509,8 +536,11 @@ impl DimConstraintCommand {
|
||||
}
|
||||
DimConstraintAxis::Horizontal => (ConstraintKind::DistanceX, DVec3::X),
|
||||
DimConstraintAxis::Vertical => (ConstraintKind::DistanceY, DVec3::Y),
|
||||
// Angular and Radius never measure here; their own steps decide.
|
||||
DimConstraintAxis::Aligned | DimConstraintAxis::Angular | DimConstraintAxis::Radius => (
|
||||
// Angular never measures here; its own steps decide the sector.
|
||||
DimConstraintAxis::Aligned
|
||||
| DimConstraintAxis::Angular
|
||||
| DimConstraintAxis::Radius
|
||||
| DimConstraintAxis::Diameter => (
|
||||
ConstraintKind::Distance,
|
||||
(second - first).normalize_or(DVec3::X),
|
||||
),
|
||||
@@ -560,9 +590,10 @@ impl DimConstraintCommand {
|
||||
DimConstraintAxis::Linear => "Linear constraint",
|
||||
DimConstraintAxis::Horizontal => "Horizontal distance constraint",
|
||||
DimConstraintAxis::Vertical => "Vertical distance constraint",
|
||||
DimConstraintAxis::Aligned | DimConstraintAxis::Angular | DimConstraintAxis::Radius => {
|
||||
"Aligned constraint"
|
||||
}
|
||||
DimConstraintAxis::Aligned
|
||||
| DimConstraintAxis::Angular
|
||||
| DimConstraintAxis::Radius
|
||||
| DimConstraintAxis::Diameter => "Aligned constraint",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -604,17 +635,22 @@ impl CadCommand for DimConstraintCommand {
|
||||
Step::AngularPoint2 { .. } => {
|
||||
format!("{name} Specify second angle constraint point:")
|
||||
}
|
||||
Step::RadialSelect => format!("{name} Select arc or circle:"),
|
||||
Step::AngularLocation { .. } | Step::Location { .. } | Step::RadialLocation { .. } => {
|
||||
Step::RadialObject => format!("{name} Select arc or circle:"),
|
||||
Step::AngularLocation { .. }
|
||||
| Step::Location { .. }
|
||||
| Step::RadialLocation { .. } => {
|
||||
format!("{name} Specify dimension line location:")
|
||||
}
|
||||
Step::AngularValue { measured, .. } | Step::RadialValue { radius: measured, .. } => {
|
||||
format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
measured_expression(measured)
|
||||
)
|
||||
}
|
||||
Step::RadialValue { measured, .. } => format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
measured_expression(measured)
|
||||
),
|
||||
Step::AngularValue { measured, .. } => format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
measured_expression(measured)
|
||||
),
|
||||
Step::Value { measured, .. } => format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
@@ -717,7 +753,7 @@ impl CadCommand for DimConstraintCommand {
|
||||
| Step::AngularVertex
|
||||
| Step::AngularPoint1 { .. }
|
||||
| Step::AngularPoint2 { .. }
|
||||
| Step::RadialSelect
|
||||
| Step::RadialObject
|
||||
)
|
||||
}
|
||||
|
||||
@@ -909,27 +945,30 @@ impl CadCommand for DimConstraintCommand {
|
||||
whole_curve: false,
|
||||
})
|
||||
}
|
||||
Step::RadialSelect => {
|
||||
Step::RadialObject => {
|
||||
if handle.is_null() {
|
||||
return Self::report(Self::NO_OBJECT);
|
||||
}
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let radius = match &entity {
|
||||
EntityType::Circle(circle) => circle.radius,
|
||||
EntityType::Arc(arc) => arc.radius,
|
||||
_ => return Self::report(Self::INVALID_RADIAL),
|
||||
let Some(target) = Self::radial_target(&entity, handle) else {
|
||||
return Self::report(&format!(
|
||||
"Invalid selection for {}. Select a circle or arc.",
|
||||
self.noun()
|
||||
));
|
||||
};
|
||||
self.step = Step::RadialLocation {
|
||||
circle: ParametricRef::whole(handle),
|
||||
radius,
|
||||
let measured = if self.axis == DimConstraintAxis::Diameter {
|
||||
target.radius * 2.0
|
||||
} else {
|
||||
target.radius
|
||||
};
|
||||
// The reference reports the radius before asking where the
|
||||
// dimension goes.
|
||||
self.step = Step::RadialLocation { target };
|
||||
// The measured size is reported before the location, as the
|
||||
// reference prints it, with trailing zeros suppressed.
|
||||
CmdResult::ReportMeasurement(format!(
|
||||
"Dimension text = {}",
|
||||
measured_expression(radius)
|
||||
measured_expression(measured)
|
||||
))
|
||||
}
|
||||
Step::Location { .. }
|
||||
@@ -1065,16 +1104,7 @@ impl CadCommand for DimConstraintCommand {
|
||||
| Step::TwoLinesFirst
|
||||
| Step::TwoLinesSecond { .. }
|
||||
| Step::AngularFirst
|
||||
| Step::AngularSecond { .. }
|
||||
| Step::RadialSelect => Self::report(Self::NO_OBJECT),
|
||||
Step::RadialLocation { circle, radius } => {
|
||||
self.step = Step::RadialValue {
|
||||
circle,
|
||||
radius,
|
||||
location: point,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
| Step::AngularSecond { .. } => Self::report(Self::NO_OBJECT),
|
||||
Step::AngularLocation { data } => {
|
||||
let frame = match data {
|
||||
AngularData::Lines { first, second } => Self::angle_frame(
|
||||
@@ -1126,6 +1156,20 @@ impl CadCommand for DimConstraintCommand {
|
||||
};
|
||||
CmdResult::ReportMeasurement(format!("Dimension text = {measured:.4}"))
|
||||
}
|
||||
Step::RadialObject => Self::report(Self::NO_OBJECT),
|
||||
Step::RadialLocation { target } => {
|
||||
let measured = if self.axis == DimConstraintAxis::Diameter {
|
||||
target.radius * 2.0
|
||||
} else {
|
||||
target.radius
|
||||
};
|
||||
self.step = Step::RadialValue {
|
||||
target,
|
||||
location: point,
|
||||
measured,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::TwoLinesParallel { .. }
|
||||
| Step::Value { .. }
|
||||
| Step::AngularValue { .. }
|
||||
@@ -1147,9 +1191,6 @@ impl CadCommand for DimConstraintCommand {
|
||||
Step::AngularValue { measured, .. } => self
|
||||
.build_angular(self.default_name.clone(), measured_expression(measured))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
Step::RadialValue { radius, .. } => self
|
||||
.build_radial(self.default_name.clone(), measured_expression(radius))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
Step::PointLinePoint => {
|
||||
self.step = Step::LineFirst;
|
||||
CmdResult::NeedPoint
|
||||
@@ -1157,6 +1198,9 @@ impl CadCommand for DimConstraintCommand {
|
||||
Step::Value { measured, .. } => self
|
||||
.build(self.default_name.clone(), measured_expression(measured))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
Step::RadialValue { measured, .. } => self
|
||||
.build_radial(self.default_name.clone(), measured_expression(measured))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,36 +353,37 @@ pub(crate) fn equal_size_follower(
|
||||
Some(entity)
|
||||
}
|
||||
|
||||
/// The first free `<prefix>1`, `<prefix>2`, … name a new constraint takes.
|
||||
fn next_prefixed_parameter_name(
|
||||
/// The first free `d1`, `d2`, … name a new dimensional constraint takes.
|
||||
pub(crate) fn next_dimensional_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
prefix: &str,
|
||||
) -> String {
|
||||
(1..)
|
||||
.map(|n| format!("d{n}"))
|
||||
.find(|name| !table.contains(name))
|
||||
.unwrap_or_else(|| "d1".to_string())
|
||||
}
|
||||
|
||||
/// The first free `rad1`/`dia1`, `rad2`/`dia2`, … name a new radius or
|
||||
/// diameter constraint takes.
|
||||
pub(crate) fn next_radial_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
diameter: bool,
|
||||
) -> String {
|
||||
let prefix = if diameter { "dia" } else { "rad" };
|
||||
(1..)
|
||||
.map(|n| format!("{prefix}{n}"))
|
||||
.find(|name| !table.contains(name))
|
||||
.unwrap_or_else(|| format!("{prefix}1"))
|
||||
}
|
||||
|
||||
/// The first free `d1`, `d2`, … name a new dimensional constraint takes.
|
||||
pub(crate) fn next_dimensional_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
) -> String {
|
||||
next_prefixed_parameter_name(table, "d")
|
||||
}
|
||||
|
||||
/// The first free `ang1`, `ang2`, … name a new angular constraint takes.
|
||||
pub(crate) fn next_angular_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
) -> String {
|
||||
next_prefixed_parameter_name(table, "ang")
|
||||
}
|
||||
|
||||
/// The first free `rad1`, `rad2`, … name a new radius constraint takes.
|
||||
pub(crate) fn next_radial_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
) -> String {
|
||||
next_prefixed_parameter_name(table, "rad")
|
||||
(1..)
|
||||
.map(|n| format!("ang{n}"))
|
||||
.find(|name| !table.contains(name))
|
||||
.unwrap_or_else(|| "ang1".to_string())
|
||||
}
|
||||
|
||||
/// An angle as the reference displays it: a negative or over-full value is
|
||||
@@ -577,52 +578,6 @@ fn dynamic_dimension_follow_points(
|
||||
true
|
||||
}
|
||||
|
||||
/// A circle's or arc's center and radius.
|
||||
fn radial_geometry(entity: &acadrust::EntityType) -> Option<(Vector3, f64)> {
|
||||
match entity {
|
||||
acadrust::EntityType::Circle(circle) => Some((circle.center_wcs(), circle.radius)),
|
||||
acadrust::EntityType::Arc(arc) => Some((arc.center_wcs(), arc.radius)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves a dynamic radial dimension onto its circle's current center and
|
||||
/// radius: the dimension line keeps its direction and the text keeps its
|
||||
/// distance beyond the circle, as in the reference; true when it moved.
|
||||
fn dynamic_dimension_follow_radius(
|
||||
dimension: &mut acadrust::entities::Dimension,
|
||||
center: Vector3,
|
||||
radius: f64,
|
||||
) -> bool {
|
||||
let acadrust::entities::Dimension::Radius(radial) = dimension else {
|
||||
return false;
|
||||
};
|
||||
let to_dvec = |p: Vector3| glam::DVec3::new(p.x, p.y, p.z);
|
||||
let to_vec = |p: glam::DVec3| Vector3::new(p.x, p.y, p.z);
|
||||
let old_center = to_dvec(radial.angle_vertex);
|
||||
let old_radius = (to_dvec(radial.definition_point) - old_center).length();
|
||||
let center = to_dvec(center);
|
||||
if (old_center - center).length_squared() < 1.0e-16 && (old_radius - radius).abs() < 1.0e-9 {
|
||||
return false;
|
||||
}
|
||||
let toward_text = to_dvec(radial.base.text_middle_point) - old_center;
|
||||
let direction = toward_text
|
||||
.try_normalize()
|
||||
.or_else(|| (to_dvec(radial.definition_point) - old_center).try_normalize())
|
||||
.unwrap_or(glam::DVec3::X);
|
||||
let beyond = toward_text.length() - old_radius;
|
||||
let chord = center + direction * radius;
|
||||
let text = center + direction * (radius + beyond);
|
||||
radial.angle_vertex = to_vec(center);
|
||||
radial.definition_point = to_vec(chord);
|
||||
radial.base.definition_point = to_vec(chord);
|
||||
radial.base.text_middle_point = to_vec(text);
|
||||
radial.base.insertion_point = to_vec(text);
|
||||
radial.leader_length = (text - chord).length();
|
||||
radial.base.actual_measurement = radius;
|
||||
true
|
||||
}
|
||||
|
||||
/// The end points of a line reference: a line's two ends, or the picked
|
||||
/// polyline segment's vertices.
|
||||
fn line_ends(entity: &acadrust::EntityType, reference: ParametricRef) -> Option<(Vector3, Vector3)> {
|
||||
@@ -659,6 +614,99 @@ fn angular_follow_points(
|
||||
}
|
||||
}
|
||||
|
||||
/// A radial constraint's circle or arc: its centre and radius now.
|
||||
fn radial_geometry(
|
||||
document: &acadrust::CadDocument,
|
||||
constraint: &ParametricConstraint,
|
||||
) -> Option<(glam::DVec3, f64)> {
|
||||
let reference = constraint.refs.first()?;
|
||||
let (center, radius) = match document.get_entity(reference.entity)? {
|
||||
acadrust::EntityType::Circle(circle) => (circle.center, circle.radius),
|
||||
acadrust::EntityType::Arc(arc) => (arc.center, arc.radius),
|
||||
_ => return None,
|
||||
};
|
||||
Some((glam::DVec3::new(center.x, center.y, center.z), radius))
|
||||
}
|
||||
|
||||
/// Rebuilds a dynamic radial dimension on its circle's current centre and
|
||||
/// radius, keeping the direction its dimension line was placed in; true
|
||||
/// when anything moved.
|
||||
fn dynamic_dimension_follow_radial(
|
||||
dimension: &mut acadrust::entities::Dimension,
|
||||
center: glam::DVec3,
|
||||
radius: f64,
|
||||
) -> bool {
|
||||
use acadrust::entities::Dimension;
|
||||
let to_dvec = |p: Vector3| glam::DVec3::new(p.x, p.y, p.z);
|
||||
let same = |a: Vector3, b: Vector3| (a - b).length_squared() < 1.0e-16;
|
||||
let text = dimension.base().user_text.clone();
|
||||
let (rebuilt, leader) = match &*dimension {
|
||||
Dimension::Radius(d) => {
|
||||
let direction = to_dvec(d.definition_point) - to_dvec(d.angle_vertex);
|
||||
let direction = direction
|
||||
.try_normalize()
|
||||
.unwrap_or(glam::DVec3::X);
|
||||
(
|
||||
crate::modules::annotate::radius_dim::radius_constraint_entity(
|
||||
center,
|
||||
radius,
|
||||
center + direction * radius.max(1.0e-9),
|
||||
text,
|
||||
),
|
||||
d.leader_length,
|
||||
)
|
||||
}
|
||||
Dimension::Diameter(d) => {
|
||||
let direction = to_dvec(d.angle_vertex) - to_dvec(d.definition_point);
|
||||
let direction = direction
|
||||
.try_normalize()
|
||||
.unwrap_or(glam::DVec3::X);
|
||||
(
|
||||
crate::modules::annotate::diameter_dim::diameter_constraint_entity(
|
||||
center,
|
||||
radius,
|
||||
center + direction * radius.max(1.0e-9),
|
||||
text,
|
||||
),
|
||||
d.leader_length,
|
||||
)
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
let Some(acadrust::EntityType::Dimension(mut rebuilt)) = rebuilt else {
|
||||
return false;
|
||||
};
|
||||
let unchanged = match (&*dimension, &rebuilt) {
|
||||
(Dimension::Radius(old), Dimension::Radius(new)) => {
|
||||
same(old.angle_vertex, new.angle_vertex)
|
||||
&& same(old.definition_point, new.definition_point)
|
||||
}
|
||||
(Dimension::Diameter(old), Dimension::Diameter(new)) => {
|
||||
same(old.angle_vertex, new.angle_vertex)
|
||||
&& same(old.definition_point, new.definition_point)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if unchanged {
|
||||
return false;
|
||||
}
|
||||
match &mut rebuilt {
|
||||
Dimension::Radius(new) => new.leader_length = leader,
|
||||
Dimension::Diameter(new) => new.leader_length = leader,
|
||||
_ => {}
|
||||
}
|
||||
let base = dimension.base();
|
||||
let target = rebuilt.base_mut();
|
||||
target.common = base.common.clone();
|
||||
target.style_name = base.style_name.clone();
|
||||
target.text_rotation = base.text_rotation;
|
||||
target.attachment_point = base.attachment_point;
|
||||
target.line_spacing_style = base.line_spacing_style;
|
||||
target.line_spacing_factor = base.line_spacing_factor;
|
||||
*dimension = rebuilt;
|
||||
true
|
||||
}
|
||||
|
||||
/// Rebuilds a dynamic angular dimension on its sides' current positions,
|
||||
/// keeping its arc point; true when anything moved.
|
||||
fn dynamic_dimension_follow_angle(
|
||||
@@ -731,9 +779,9 @@ pub(crate) fn dimensional_anchor_refs(
|
||||
let Some(&first) = refs.first() else {
|
||||
return Vec::new();
|
||||
};
|
||||
// A whole circle or arc (a radius or diameter): its center stays and the
|
||||
// value resizes it. A single whole reference can also be a line's length,
|
||||
// whose start is what stays, so the entity decides.
|
||||
// A whole circle or arc (a radius or diameter): its centre stays and the
|
||||
// value resizes it. This is what a parameter edit through -PARAMETERS or
|
||||
// Properties goes through; the command itself passes the anchor directly.
|
||||
if refs.len() == 1
|
||||
&& first.marker.is_none()
|
||||
&& matches!(
|
||||
@@ -2053,7 +2101,7 @@ impl super::Scene {
|
||||
String,
|
||||
Option<(Vector3, Vector3)>,
|
||||
Option<Vec<Vector3>>,
|
||||
Option<(Vector3, f64)>,
|
||||
Option<(glam::DVec3, f64)>,
|
||||
)> = Vec::new();
|
||||
// A reference (driven) constraint's parameter follows the geometry.
|
||||
let mut followed: Vec<(String, f64)> = Vec::new();
|
||||
@@ -2081,15 +2129,21 @@ impl super::Scene {
|
||||
constraint.kind,
|
||||
ConstraintKind::Angle | ConstraintKind::Angle3Point
|
||||
);
|
||||
// A radius: the circle's or arc's center and radius now.
|
||||
let radial = (constraint.kind == ConstraintKind::Radius)
|
||||
.then(|| {
|
||||
let reference = constraint.refs.first()?;
|
||||
radial_geometry(self.document.get_entity(reference.entity)?)
|
||||
})
|
||||
let radial = matches!(
|
||||
constraint.kind,
|
||||
ConstraintKind::Radius | ConstraintKind::Diameter
|
||||
);
|
||||
let radial_now = radial
|
||||
.then(|| radial_geometry(&self.document, constraint))
|
||||
.flatten();
|
||||
let measured = radial.map(|(_, radius)| radius);
|
||||
let measured = measured.or(points.filter(|_| !angular).map(|(first, second)| {
|
||||
let radial_measured = radial_now.map(|(_, radius)| {
|
||||
if constraint.kind == ConstraintKind::Diameter {
|
||||
radius * 2.0
|
||||
} else {
|
||||
radius
|
||||
}
|
||||
});
|
||||
let measured = points.filter(|_| !angular).map(|(first, second)| {
|
||||
let delta = second - first;
|
||||
let axis = match self.document.get_entity(*dimension) {
|
||||
Some(acadrust::EntityType::Dimension(
|
||||
@@ -2105,7 +2159,8 @@ impl super::Scene {
|
||||
}
|
||||
_ => delta.length(),
|
||||
}
|
||||
}));
|
||||
});
|
||||
let measured = radial_measured.or(measured);
|
||||
let (name, value, source) = match &constraint.driving_param {
|
||||
Some(DrivingValue::Named(name)) => {
|
||||
let value = match (reference, measured) {
|
||||
@@ -2147,9 +2202,11 @@ impl super::Scene {
|
||||
annotational,
|
||||
decimals,
|
||||
),
|
||||
points,
|
||||
// A radial dimension follows its own circle, not a pair
|
||||
// of constraint points.
|
||||
points.filter(|_| !radial),
|
||||
angle_points,
|
||||
radial,
|
||||
radial_now,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2177,7 +2234,7 @@ impl super::Scene {
|
||||
}
|
||||
}
|
||||
if let Some((center, radius)) = radial {
|
||||
if dynamic_dimension_follow_radius(&mut dimension, center, radius) {
|
||||
if dynamic_dimension_follow_radial(&mut dimension, center, radius) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -2343,6 +2400,7 @@ impl super::Scene {
|
||||
ov::DIMEXO,
|
||||
ov::DIMEXE,
|
||||
ov::DIMASZ,
|
||||
ov::DIMTAD,
|
||||
ov::DIMTIH,
|
||||
ov::DIMTOH,
|
||||
] {
|
||||
@@ -2381,7 +2439,7 @@ impl super::Scene {
|
||||
continue;
|
||||
}
|
||||
let style_name = dimension.base().style_name.clone();
|
||||
let text_height = self
|
||||
let style = self
|
||||
.document
|
||||
.dim_styles
|
||||
.iter()
|
||||
@@ -2389,23 +2447,40 @@ impl super::Scene {
|
||||
style.name.eq_ignore_ascii_case(&style_name)
|
||||
|| (style_name.trim().is_empty()
|
||||
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||
})
|
||||
});
|
||||
let text_height = style
|
||||
.map(|style| style.dimtxt)
|
||||
.filter(|height| *height > 1e-9)
|
||||
.unwrap_or(0.18);
|
||||
let size_of = |value: Option<f64>, fallback: f64| {
|
||||
value.filter(|size| *size > 1e-9).unwrap_or(fallback)
|
||||
};
|
||||
let arrow_size = size_of(style.map(|style| style.dimasz), text_height);
|
||||
let extension_over = size_of(style.map(|style| style.dimexe), text_height * 0.5);
|
||||
let extension_offset = size_of(style.map(|style| style.dimexo), text_height * 0.25);
|
||||
let scale = f64::from(wpp) * f64::from(DYNAMIC_DIMENSION_TEXT_PX) / text_height;
|
||||
use crate::entities::dim_override as ov;
|
||||
let xdata = &dimension.base().common.extended_data;
|
||||
let current = ov::real(xdata, ov::DIMSCALE);
|
||||
// Text gap, extension offset and overshoot and arrow size follow
|
||||
// the text height on screen (the reference's ratios), whatever
|
||||
// the style says in drawing units.
|
||||
// The gap hugs the text, so it follows the text's screen size.
|
||||
// Arrowheads and extension lines keep the style's size in
|
||||
// drawing units: dividing by the screen factor the style's
|
||||
// DIMSCALE re-applies leaves them unchanged as the view zooms.
|
||||
let sizes = [
|
||||
(ov::DIMGAP, text_height * 0.25),
|
||||
(ov::DIMEXO, text_height * 0.25),
|
||||
(ov::DIMEXE, text_height * 0.5),
|
||||
(ov::DIMASZ, text_height),
|
||||
(ov::DIMEXO, extension_offset / scale),
|
||||
(ov::DIMEXE, extension_over / scale),
|
||||
(ov::DIMASZ, arrow_size / scale),
|
||||
];
|
||||
// A radius or diameter constraint reads on its own dimension
|
||||
// line, which breaks around the text, so the text is centred on
|
||||
// it rather than lifted above it.
|
||||
let radial = matches!(
|
||||
dimension,
|
||||
acadrust::entities::Dimension::Radius(_)
|
||||
| acadrust::entities::Dimension::Diameter(_)
|
||||
);
|
||||
let centred = !radial || ov::int(xdata, ov::DIMTAD) == Some(0);
|
||||
let sizes_set = sizes.iter().all(|(code, size)| {
|
||||
ov::real(xdata, *code).is_some_and(|value| (value - size).abs() < 1e-12)
|
||||
});
|
||||
@@ -2418,9 +2493,21 @@ impl super::Scene {
|
||||
);
|
||||
let horizontal = angular
|
||||
|| (ov::int(xdata, ov::DIMTIH) == Some(1) && ov::int(xdata, ov::DIMTOH) == Some(1));
|
||||
if current.is_some_and(|value| (value - scale).abs() < 1e-9) && horizontal && sizes_set {
|
||||
if current.is_some_and(|value| (value - scale).abs() < 1e-9)
|
||||
&& horizontal
|
||||
&& sizes_set
|
||||
&& centred
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if radial && !centred {
|
||||
ov::set(
|
||||
&mut self.document,
|
||||
handle,
|
||||
ov::DIMTAD,
|
||||
Some(acadrust::xdata::XDataValue::Integer16(0)),
|
||||
);
|
||||
}
|
||||
ov::set(
|
||||
&mut self.document,
|
||||
handle,
|
||||
|
||||
Reference in new issue
Block a user