Merge pull request #886 from ramox81/codex/complete-jogged-radius-dimension

Complete jogged radius dimension workflow
This commit is contained in:
Hakan Seven 2026-08-27 08:12:28 +03:00 committed by GitHub
commit ba91095bcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 747 additions and 85 deletions

View file

@ -1864,6 +1864,12 @@ annotate =
.dimjogged-created-a-jogged-radius-dimension = DIMJOGGED: created a jogged radius dimension.
.dimjogged-invalid-radius = DIMJOGGED: invalid radius.
.dimjogged-select-an-arc-or-circle-first = DIMJOGGED: select an arc or circle first.
.dimjogged-enter-dimension-text-blank-measured = DIMJOGGED Enter dimension text (blank = measured value):
.dimjogged-select-arc-circle-or-polyline-arc = DIMJOGGED Select arc, circle, or polyline arc:
.dimjogged-specify-center-location-override = DIMJOGGED Specify center location override:
.dimjogged-specify-dimension-line-location-text = DIMJOGGED Specify dimension line location [Mtext/Text/Angle]:
.dimjogged-specify-jog-location = DIMJOGGED Specify jog location:
.dimjogged-specify-text-angle-degrees = DIMJOGGED Specify text angle (degrees):
.dimjogline-select-linear-or-aligned-dimension = DIMJOGLINE Select linear or aligned dimension:
.dimjogline-specify-jog-location = DIMJOGLINE Specify jog location:
.dimlinear-enter-dimension-text-blank-measured = DIMLINEAR Enter dimension text (blank = measured value):
@ -2667,6 +2673,7 @@ properties =
.island-detection-style = Island detection style
.italic = Italic
.jog-angle = Jog Angle
.jogged-radius = Jogged Radius
.jog-x = Jog X
.jog-y = Jog Y
.jog-z = Jog Z

View file

@ -1844,6 +1844,12 @@ annotate =
.dimjogged-created-a-jogged-radius-dimension = DIMJOGGED: kırıklı yarıçap ölçüsü oluşturuldu.
.dimjogged-invalid-radius = DIMJOGGED: yarıçap geçersiz.
.dimjogged-select-an-arc-or-circle-first = DIMJOGGED: önce bir yay veya daire seçin.
.dimjogged-enter-dimension-text-blank-measured = DIMJOGGED Ölçü metnini girin (boş = ölçülen değer):
.dimjogged-select-arc-circle-or-polyline-arc = DIMJOGGED Yay, daire veya çoklu çizgi yayı seçin:
.dimjogged-specify-center-location-override = DIMJOGGED Geçersiz kılınmış merkez konumunu belirtin:
.dimjogged-specify-dimension-line-location-text = DIMJOGGED Ölçü çizgisinin konumunu belirtin [Çok satırlı metin/Metin/Açı]:
.dimjogged-specify-jog-location = DIMJOGGED Kırılma konumunu belirtin:
.dimjogged-specify-text-angle-degrees = DIMJOGGED Metin açısını belirtin (derece):
.dimjogline-select-linear-or-aligned-dimension = DIMJOGLINE Doğrusal veya hizalı ölçüyü seçin:
.dimjogline-specify-jog-location = DIMJOGLINE Kırılma konumunu belirtin:
.dimlinear-enter-dimension-text-blank-measured = DIMLINEAR Ölçü metnini girin (boş = ölçülen değer):
@ -2606,6 +2612,7 @@ properties =
.island-detection-style = Ada algılama stili
.italic = İtalik
.jog-angle = Kırılma Açısı
.jogged-radius = Kırıklı Yarıçap
.jog-x = Kırılma X
.jog-y = Kırılma Y
.jog-z = Kırılma Z

View file

@ -48,6 +48,17 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"DIMJOGGED" | "DIMJOG" => {
use crate::modules::annotate::jogged_radius_dim::JoggedRadiusDimensionCommand;
let defaults = crate::scene::creation_style::current_dimension_defaults(
&self.tabs[i].scene.document,
);
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
let new_cmd = JoggedRadiusDimensionCommand::new(defaults, multiplier);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"DIMANGULAR" => {
use crate::modules::annotate::angular_dim::AngularDimensionCommand;
let new_cmd = AngularDimensionCommand::new();
@ -1293,49 +1304,6 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
// DIMJOGGED <DJO> — create a jogged radius dimension on the selected
// arc/circle: a standard radius dim carrying an OCS_JOGGED XData marker
// that the geometry generator renders with a foreshortened zig-zag.
"DIMJOGGED" | "DIMJOG" => {
use acadrust::entities::{Dimension, DimensionRadius};
use acadrust::types::Vector3;
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
let found =
self.tabs[i]
.scene
.selected_entities()
.iter()
.find_map(|(_, e)| match e {
acadrust::EntityType::Arc(a) => Some((a.center, a.radius)),
acadrust::EntityType::Circle(c) => Some((c.center, c.radius)),
_ => None,
});
let Some((center, radius)) = found else {
self.command_line
.push_error(crate::t!("DIMJOGGED: select an arc or circle first.").as_ref());
return None;
};
if radius <= 0.0 {
self.command_line.push_error(crate::t!("DIMJOGGED: invalid radius.").as_ref());
return None;
}
let k = std::f64::consts::FRAC_1_SQRT_2;
let arc_point =
Vector3::new(center.x + radius * k, center.y + radius * k, center.z);
let mut dim = DimensionRadius::new(center, arc_point);
dim.base.common.layer = self.tabs[i].active_layer.clone();
let mut rec = ExtendedDataRecord::new("OCS_JOGGED");
rec.add_value(XDataValue::String("1".to_string()));
dim.base.common.extended_data.add_record(rec);
self.push_undo_snapshot(i, "DIMJOGGED");
self.tabs[i]
.scene
.add_entity(acadrust::EntityType::Dimension(Dimension::Radius(dim)));
self.tabs[i].dirty = true;
self.command_line
.push_output(crate::t!("DIMJOGGED: created a jogged radius dimension.").as_ref());
}
// ARCTEXT <text> — lay the text out as one Text entity per character
// along the selected arc, each rotated to follow the tangent.
"ARCTEXT" => {

View file

@ -1031,15 +1031,13 @@ impl OpenCADStudio {
if !dim_style_names.is_empty() {
// Current style is already shown as text in the geom section;
// replace/upgrade it to a Choice if we have a list.
if let Some(geom) = sections.last_mut() {
// Replace the style name with a choice.
if let Some(prop) =
geom.props.iter_mut().find(|p| p.field == "style_name")
if let Some(prop) = sections
.iter_mut()
.flat_map(|section| section.props.iter_mut())
.find(|property| property.field == "style_name")
{
let current = match &prop.value {
crate::scene::model::object::PropValue::PlainText(s) => {
s.clone()
}
crate::scene::model::object::PropValue::PlainText(s) => s.clone(),
_ => String::new(),
};
prop.value = crate::scene::model::object::PropValue::Choice {
@ -1048,7 +1046,6 @@ impl OpenCADStudio {
};
}
}
}
// Append the resolved dimension style's groups
// (Lines & Arrows, Text, Fit, Units, Tolerances).
@ -1066,6 +1063,16 @@ impl OpenCADStudio {
&self.tabs[i].scene.document,
));
if matches!(d, acadrust::entities::Dimension::LargeRadial(_)) {
if let Some(index) = sections
.iter()
.position(|section| section.title == t!("Misc").as_ref())
{
let misc = sections.remove(index);
sections.push(misc);
}
}
// Prefer entity-level dimension-variable overrides;
// fall back to the assigned style values.
use crate::entities::dim_override as dov;

View file

@ -108,6 +108,52 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
],
}];
}
if let Dimension::LargeRadial(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()),
},
edit(t!("Center X").as_ref(), "definition_x", d.definition_point.x),
edit(t!("Center Y").as_ref(), "definition_y", d.definition_point.y),
edit(t!("Center Z").as_ref(), "definition_z", d.definition_point.z),
edit(t!("Chord X").as_ref(), "chord_x", d.chord_point.x),
edit(t!("Chord Y").as_ref(), "chord_y", d.chord_point.y),
edit(t!("Chord Z").as_ref(), "chord_z", d.chord_point.z),
edit(
t!("Override Center X").as_ref(),
"override_x",
d.override_center.x,
),
edit(
t!("Override Center Y").as_ref(),
"override_y",
d.override_center.y,
),
edit(
t!("Override Center Z").as_ref(),
"override_z",
d.override_center.z,
),
edit(t!("Jog X").as_ref(), "jog_x", d.jog_point.x),
edit(t!("Jog Y").as_ref(), "jog_y", d.jog_point.y),
edit(t!("Jog Z").as_ref(), "jog_z", d.jog_point.z),
edit_angle(
t!("Jog Angle").as_ref(),
"jog_angle",
d.jog_angle.to_degrees(),
),
ro(
t!("Measurement").as_ref(),
"measurement",
format!("{:.4}", d.measurement()),
),
],
}];
}
let compact_linear = match dim {
Dimension::Linear(d) => Some((
&d.base,
@ -281,21 +327,7 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
props.push(edit(t!("Leader 2 Y").as_ref(), "leader2_y", d.second_leader_point.y));
props.push(edit(t!("Leader 2 Z").as_ref(), "leader2_z", d.second_leader_point.z));
}
Dimension::LargeRadial(d) => {
props.push(edit(t!("Center X").as_ref(), "definition_x", d.definition_point.x));
props.push(edit(t!("Center Y").as_ref(), "definition_y", d.definition_point.y));
props.push(edit(t!("Center Z").as_ref(), "definition_z", d.definition_point.z));
props.push(edit(t!("Chord X").as_ref(), "chord_x", d.chord_point.x));
props.push(edit(t!("Chord Y").as_ref(), "chord_y", d.chord_point.y));
props.push(edit(t!("Chord Z").as_ref(), "chord_z", d.chord_point.z));
props.push(edit(t!("Override Center X").as_ref(), "override_x", d.override_center.x));
props.push(edit(t!("Override Center Y").as_ref(), "override_y", d.override_center.y));
props.push(edit(t!("Override Center Z").as_ref(), "override_z", d.override_center.z));
props.push(edit(t!("Jog X").as_ref(), "jog_x", d.jog_point.x));
props.push(edit(t!("Jog Y").as_ref(), "jog_y", d.jog_point.y));
props.push(edit(t!("Jog Z").as_ref(), "jog_z", d.jog_point.z));
props.push(edit_angle(t!("Jog Angle").as_ref(), "jog_angle", d.jog_angle.to_degrees()));
}
Dimension::LargeRadial(_) => unreachable!("handled by compact large-radial properties"),
}
vec![PropSection {
title: t!("Geometry").into_owned(),
@ -1828,7 +1860,7 @@ pub fn style_sections(
let linetype = |code, inherited| {
linetype_name(document, ov::handle(data, code).unwrap_or(inherited))
};
let arrow_1 = if matches!(dimension, Dimension::Radius(_)) {
let arrow_1 = if matches!(dimension, Dimension::Radius(_) | Dimension::LargeRadial(_)) {
let inherited = if s.dimblk1.is_null() { s.dimblk } else { s.dimblk1 };
let inherited_name = if s.dimblk1.is_null() {
&s.dimblk_name
@ -2530,7 +2562,10 @@ pub fn style_sections(
],
},
];
if matches!(dimension, Dimension::Radius(_) | Dimension::Diameter(_)) {
if matches!(
dimension,
Dimension::Radius(_) | Dimension::Diameter(_) | Dimension::LargeRadial(_)
) {
let dimcen = real(ov::DIMCEN, s.dimcen);
let center_type = if dimcen > 1e-12 {
"Center marks"
@ -2573,8 +2608,18 @@ pub fn style_sections(
"dim_ext_line_ext",
"dim_ext_line_offset",
];
const LARGE_RADIAL_LINE_FIELDS: &[&str] = &[
"dim_arrowhead_1",
"dim_arrow_size",
"dim_line_lineweight",
"dim_line_1",
"dim_line_color",
"dim_linetype",
];
let retained = if matches!(dimension, Dimension::Diameter(_)) {
DIAMETER_LINE_FIELDS
} else if matches!(dimension, Dimension::LargeRadial(_)) {
LARGE_RADIAL_LINE_FIELDS
} else {
RADIUS_LINE_FIELDS
};
@ -3097,6 +3142,28 @@ fn tessellate_dimension_inner(
return wires;
}
}
if let Dimension::LargeRadial(radial) = dim {
if let Some((local_radial, normal)) = large_radial_dimension_in_ocs(radial) {
let mut wires = tessellate_dimension_inner(
document,
handle,
&Dimension::LargeRadial(local_radial),
selected,
entity_color,
line_weight_px,
anno_scale,
selected_set,
active_viewport,
bg_color,
None,
world_per_pixel,
);
for wire in &mut wires {
map_wire_ocs_to_wcs(wire, normal);
}
return wires;
}
}
let name = handle.value().to_string();
// (Baked-block fast path moved up into scene::tessellate_entity so the
// recursive call goes through the LOD ladder, not the kernel path.)
@ -3464,8 +3531,7 @@ fn tessellate_dimension_inner(
// DIMUPT governs interactive creation-time text placement; saved
// geometry already carries the resulting position.
let _ = s.dimupt;
// DIMJOGANG is consumed by the jogged-radius path.
let _ = s.dimjogang;
let _ = (s.dimarcsym, s.dimjogang);
// DIMUNIT is the obsolete pre-R2000 linear unit format; DIMLUNIT
// supersedes it. Read but not honoured.
let _ = s.dimunit;
@ -4345,15 +4411,23 @@ fn dimension_geometry(
let override_center = lv(d.override_center);
let (near, far) =
jogged_radial_break(chord, jog, override_center, d.jog_angle as f32);
if !suppress.dim1 {
add_segment(&mut g.dim_lines, chord, near);
add_segment(&mut g.dim_lines, near, far);
add_segment(&mut g.dim_lines, far, override_center);
}
append_arrow(
&mut g,
chord,
normalized_or(near - chord, Vec3::X),
arrow1,
);
append_center_mark(
&mut g,
lv(d.definition_point),
params.dimcen,
(chord - lv(d.definition_point)).length(),
);
}
}
g
@ -4789,6 +4863,43 @@ pub(crate) fn arc_dimension_in_ocs(
Some((local, normal))
}
pub(crate) fn large_radial_dimension_in_ocs(
dimension: &DimensionLargeRadial,
) -> Option<(DimensionLargeRadial, Vector3)> {
let length = dimension.base.normal.length();
if !length.is_finite() || length <= 1.0e-12 {
return None;
}
let normal = dimension.base.normal / length;
if (normal - Vector3::UNIT_Z).length() <= 1.0e-12 {
return None;
}
let normal_tuple = (normal.x, normal.y, normal.z);
let to_ocs = |point: Vector3| {
let point = crate::scene::view::transform::wcs_point_to_ocs(
(point.x, point.y, point.z),
normal_tuple,
);
Vector3::new(point.0, point.1, point.2)
};
let mut local = dimension.clone();
local.definition_point = to_ocs(dimension.definition_point);
let elevation = local.definition_point.z;
let on_plane = |point: Vector3| {
let mut point = to_ocs(point);
point.z = elevation;
point
};
local.chord_point = on_plane(dimension.chord_point);
local.override_center = on_plane(dimension.override_center);
local.jog_point = on_plane(dimension.jog_point);
local.base.definition_point = local.definition_point;
local.base.text_middle_point = on_plane(dimension.base.text_middle_point);
local.base.insertion_point = on_plane(dimension.base.insertion_point);
local.base.normal = Vector3::UNIT_Z;
Some((local, normal))
}
fn map_wire_ocs_to_wcs(wire: &mut WireModel, normal: Vector3) {
let normal_tuple = (normal.x, normal.y, normal.z);
let map = |x: f64, y: f64, z: f64| {

View file

@ -767,6 +767,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"DIMJOGGED: created a jogged radius dimension." => Some(("annotate", "dimjogged-created-a-jogged-radius-dimension")),
"DIMJOGGED: invalid radius." => Some(("annotate", "dimjogged-invalid-radius")),
"DIMJOGGED: select an arc or circle first." => Some(("annotate", "dimjogged-select-an-arc-or-circle-first")),
"DIMJOGGED Enter dimension text (blank = measured value):" => Some(("annotate", "dimjogged-enter-dimension-text-blank-measured")),
"DIMJOGGED Select arc, circle, or polyline arc:" => Some(("annotate", "dimjogged-select-arc-circle-or-polyline-arc")),
"DIMJOGGED Specify center location override:" => Some(("annotate", "dimjogged-specify-center-location-override")),
"DIMJOGGED Specify dimension line location [Mtext/Text/Angle]:" => Some(("annotate", "dimjogged-specify-dimension-line-location-text")),
"DIMJOGGED Specify jog location:" => Some(("annotate", "dimjogged-specify-jog-location")),
"DIMJOGGED Specify text angle (degrees):" => Some(("annotate", "dimjogged-specify-text-angle-degrees")),
"DIMJOGLINE Select linear or aligned dimension:" => Some(("annotate", "dimjogline-select-linear-or-aligned-dimension")),
"DIMJOGLINE Specify jog location:" => Some(("annotate", "dimjogline-specify-jog-location")),
"DIMJOGLINE: not yet implemented — nothing changed." => Some(("common", "dimjogline-not-yet-implemented-nothing-changed")),
@ -1298,6 +1304,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"JUSTIFYTEXT: updated {n} text object(s)." => Some(("annotate", "justifytext-updated-n-text-object-s")),
"Jog Angle" => Some(("properties", "jog-angle")),
"Jog Line" => Some(("annotate", "jog-line")),
"Jogged Radius" => Some(("common", "jogged-radius")),
"Jog X" => Some(("properties", "jog-x")),
"Jog Y" => Some(("properties", "jog-y")),
"Jog Z" => Some(("properties", "jog-z")),

View file

@ -0,0 +1,479 @@
use acadrust::entities::{Dimension, DimensionLargeRadial};
use acadrust::types::{Handle, Vector3};
use acadrust::EntityType;
use glam::{DVec3, Vec3};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource,
WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::creation_style::DimensionCreationDefaults;
use crate::scene::dimension_assoc::RadialSourceGeometry;
use crate::scene::model::wire_model::WireModel;
use crate::t;
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/dim_jog.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "DIMJOGGED",
label: "Jogged Radius",
icon: ICON,
event: ModuleEvent::Command("DIMJOGGED".to_string()),
}
}
#[derive(Clone, Copy)]
enum Step {
SelectObject,
OverrideCenter(RadialSourceGeometry),
DimLine {
source: RadialSourceGeometry,
override_center: DVec3,
},
Jog {
source: RadialSourceGeometry,
override_center: DVec3,
chord: DVec3,
text_position: DVec3,
},
}
pub struct JoggedRadiusDimensionCommand {
step: Step,
defaults: DimensionCreationDefaults,
editor_height: f64,
text_override: Option<String>,
text_angle: Option<f64>,
awaiting_text: bool,
awaiting_angle: bool,
picked_entity: Option<EntityType>,
source_handle: Option<Handle>,
}
impl JoggedRadiusDimensionCommand {
pub fn new(defaults: DimensionCreationDefaults, annotation_multiplier: f64) -> Self {
let display_scale = if defaults.annotative {
annotation_multiplier.max(f64::EPSILON)
} else {
defaults.scale
};
let editor_height = defaults.text_height * display_scale;
Self {
step: Step::SelectObject,
defaults,
editor_height,
text_override: None,
text_angle: None,
awaiting_text: false,
awaiting_angle: false,
picked_entity: None,
source_handle: None,
}
}
fn editor_anchor(&self) -> DVec3 {
match self.step {
Step::SelectObject => DVec3::ZERO,
Step::OverrideCenter(source) => dvec(source.point_at_angle(source.start_angle)),
Step::DimLine {
override_center, ..
}
| Step::Jog {
override_center, ..
} => override_center,
}
}
fn commit_dimension(
&self,
source: RadialSourceGeometry,
override_center_world: DVec3,
chord_world: DVec3,
text_world: DVec3,
jog_world: DVec3,
) -> CmdResult {
let plane = source_plane(source);
let center = plane.to_local(dvec(source.center_world()));
let chord = plane.to_local(chord_world);
let override_center = plane.to_local(override_center_world);
let jog = project_jog(override_center, chord, plane.to_local(jog_world));
let text_position = plane.to_local(text_world);
let mut dimension = DimensionLargeRadial::default();
dimension.definition_point = v3(center);
dimension.base.definition_point = v3(center);
dimension.chord_point = v3(chord);
dimension.override_center = v3(override_center);
dimension.jog_point = v3(jog);
dimension.jog_angle = self.defaults.jog_angle;
dimension.base.style_name.clone_from(&self.defaults.style_name);
dimension.base.text_middle_point = v3(text_position);
dimension.base.insertion_point = v3(text_position);
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(vec![Some(
DimensionAssociationSource::explicit(
handle,
source.marker,
source.angle_at(chord_world.to_array()),
),
)])
},
);
CmdResult::CommitDimension {
entity: plane.place_entity(EntityType::Dimension(Dimension::LargeRadial(
dimension,
))),
association,
}
}
}
impl CadCommand for JoggedRadiusDimensionCommand {
fn set_working_plane(&mut self, _plane: WorkingPlane) {}
fn name(&self) -> &'static str {
"DIMJOGGED"
}
fn prompt(&self) -> String {
if self.awaiting_text {
return t!("DIMJOGGED Enter dimension text (blank = measured value):").into_owned();
}
if self.awaiting_angle {
return t!("DIMJOGGED Specify text angle (degrees):").into_owned();
}
match self.step {
Step::SelectObject => {
t!("DIMJOGGED Select arc, circle, or polyline arc:").into_owned()
}
Step::OverrideCenter(_) => {
t!("DIMJOGGED Specify center location override:").into_owned()
}
Step::DimLine { .. } => t!(
"DIMJOGGED Specify dimension line location [Mtext/Text/Angle]:"
)
.into_owned(),
Step::Jog { .. } => t!("DIMJOGGED Specify jog location:").into_owned(),
}
}
fn on_point(&mut self, point: DVec3) -> CmdResult {
match self.step {
Step::SelectObject => CmdResult::NeedPoint,
Step::OverrideCenter(source) => {
self.step = Step::DimLine {
source,
override_center: point,
};
CmdResult::NeedPoint
}
Step::DimLine {
source,
override_center,
} => {
self.step = Step::Jog {
source,
override_center,
chord: dvec(source.chord_at(point.to_array())),
text_position: point,
};
CmdResult::NeedPoint
}
Step::Jog {
source,
override_center,
chord,
text_position,
} => self.commit_dimension(
source,
override_center,
chord,
text_position,
point,
),
}
}
fn on_enter(&mut self) -> CmdResult {
if self.awaiting_text {
self.awaiting_text = false;
return CmdResult::NeedPoint;
}
if self.awaiting_angle {
self.awaiting_angle = false;
return CmdResult::NeedPoint;
}
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> {
if matches!(self.step, Step::DimLine { .. })
&& !self.awaiting_text
&& !self.awaiting_angle
{
vec![
CmdOption::new("MText", "MTEXT"),
CmdOption::new("Text", "TEXT"),
CmdOption::new("Angle", "ANGLE"),
]
} else {
Vec::new()
}
}
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);
}
if !matches!(self.step, Step::DimLine { .. }) {
return None;
}
match text.trim().to_ascii_uppercase().as_str() {
"T" | "TEXT" => {
self.awaiting_text = true;
Some(CmdResult::NeedPoint)
}
"M" | "MTEXT" => Some(CmdResult::SuspendForMTextInput {
pos: self.editor_anchor(),
initial: self.text_override.clone().unwrap_or_default(),
height: self.editor_height,
}),
"A" | "ANGLE" => {
self.awaiting_angle = true;
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.radius.is_finite() || source.radius <= 1e-12 {
return CmdResult::NeedPoint;
}
self.source_handle = Some(handle);
self.step = Step::OverrideCenter(source);
CmdResult::NeedPoint
}
fn on_mouse_move(&mut self, point: DVec3) -> Option<WireModel> {
match self.step {
Step::SelectObject => None,
Step::OverrideCenter(source) => Some(preview_wire(vec![
dvec(source.center_world()).as_vec3(),
point.as_vec3(),
])),
Step::DimLine {
source,
override_center,
} => {
let chord = dvec(source.chord_at(point.to_array()));
Some(preview_wire(vec![
override_center.as_vec3(),
chord.as_vec3(),
Vec3::splat(f32::NAN),
chord.as_vec3(),
point.as_vec3(),
]))
}
Step::Jog {
source,
override_center,
chord,
..
} => {
let plane = source_plane(source);
let chord_local = plane.to_local(chord);
let override_local = plane.to_local(override_center);
let jog_local = project_jog(override_local, chord_local, plane.to_local(point));
let (near, far) = jog_break(
chord_local,
jog_local,
override_local,
self.defaults.jog_angle,
);
Some(preview_wire(
[chord_local, near, far, override_local]
.into_iter()
.map(|position| plane.to_world(position).as_vec3())
.collect(),
))
}
}
}
}
fn source_plane(source: RadialSourceGeometry) -> WorkingPlane {
WorkingPlane::new(
DVec3::from_array(source.plane.origin),
DVec3::from_array(source.plane.x_axis),
DVec3::from_array(source.plane.y_axis),
)
}
fn project_jog(override_center: DVec3, chord: DVec3, point: DVec3) -> DVec3 {
let line = chord - override_center;
let length_squared = line.length_squared();
if length_squared <= 1e-18 {
return override_center;
}
let factor = ((point - override_center).dot(line) / length_squared).clamp(0.0, 1.0);
override_center + line * factor
}
fn jog_break(
chord: DVec3,
jog: DVec3,
override_center: DVec3,
jog_angle: f64,
) -> (DVec3, DVec3) {
let radial = (chord - override_center).normalize_or_zero();
let (sin, cos) = jog_angle.sin_cos();
let transverse = DVec3::new(
radial.x * cos - radial.y * sin,
radial.x * sin + radial.y * cos,
0.0,
)
.normalize_or_zero();
let half = ((chord - override_center).length() * 0.04).max(1e-3);
let first = jog - transverse * half;
let second = jog + transverse * half;
if chord.distance_squared(first) <= chord.distance_squared(second) {
(first, second)
} else {
(second, first)
}
}
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 preview_wire(points: Vec<Vec3>) -> 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: "dimjogged_preview".to_string(),
points: points.into_iter().map(|point| point.to_array()).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::new(),
tangent_geoms: Vec::new(),
aci: 0,
key_vertices: Vec::new(),
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
fill_tris: Vec::new(),
fill_tris_low: Vec::new(),
}
}
inventory::submit!(crate::command::CommandRegistration {
names: &["DIMJOGGED", "DIMJOG"]
});

View file

@ -16,6 +16,7 @@ pub mod dimspace;
pub mod dimtedit;
pub mod leader_cmd;
pub mod linear_dim;
pub mod jogged_radius_dim;
pub mod mleader_cmd;
pub mod mleader_edit;
pub mod mtext;
@ -109,6 +110,11 @@ impl CadModule for AnnotateModule {
radius_dim::tool().label,
radius_dim::tool().icon,
),
(
jogged_radius_dim::tool().id,
jogged_radius_dim::tool().label,
jogged_radius_dim::tool().icon,
),
(
diameter_dim::tool().id,
diameter_dim::tool().label,

View file

@ -927,6 +927,24 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
.collect();
}
}
if let Dimension::LargeRadial(radial) = dim {
if let Some((local_radial, normal)) =
crate::entities::dimension::large_radial_dimension_in_ocs(radial)
{
let (x_axis, y_axis) = crate::scene::view::transform::ocs_axes((
normal.x, normal.y, normal.z,
));
let plane = WorkingPlane::new(
DVec3::ZERO,
DVec3::new(x_axis.0, x_axis.1, x_axis.2),
DVec3::new(y_axis.0, y_axis.1, y_axis.2),
);
return explode_dimension(&Dimension::LargeRadial(local_radial), doc)
.into_iter()
.map(|entity| plane.place_entity(entity))
.collect();
}
}
let base = dim.base();
let met = dim_metrics(dim, doc);
@ -1235,9 +1253,11 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
d.override_center,
d.jog_angle,
);
if !met.dimsd1 {
result.push(make_seg(&d.chord_point, &near, &dim_c));
result.push(make_seg(&near, &far, &dim_c));
result.push(make_seg(&far, &d.override_center, &dim_c));
}
let len = ((near.x - d.chord_point.x).powi(2)
+ (near.y - d.chord_point.y).powi(2))
.sqrt()
@ -1249,6 +1269,15 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
&met.arrow1,
&dim_c,
));
let radius = ((d.definition_point.x - d.chord_point.x).powi(2)
+ (d.definition_point.y - d.chord_point.y).powi(2))
.sqrt();
result.extend(dim_center_mark(
d.definition_point,
met.dimcen,
radius,
&dim_c,
));
}
}

View file

@ -18,6 +18,7 @@ pub struct DimensionCreationDefaults {
pub text_height: f64,
pub gap: f64,
pub arrow_size: f64,
pub jog_angle: f64,
pub scale: f64,
pub annotative: bool,
}
@ -92,6 +93,9 @@ pub fn current_dimension_defaults(doc: &CadDocument) -> DimensionCreationDefault
text_height: style.as_ref().map_or(2.5, |style| style.dimtxt),
gap: style.as_ref().map_or(0.625, |style| style.dimgap),
arrow_size: style.as_ref().map_or(2.5, |style| style.dimasz),
jog_angle: style
.as_ref()
.map_or(std::f64::consts::FRAC_PI_4, |style| style.dimjogang),
scale: style.as_ref().map_or(1.0, |style| {
if style.annotative {
1.0

View file

@ -559,6 +559,7 @@ pub(crate) fn radial_extension_points(
let target_point = match dimension_entity {
Dimension::Radius(radius) => radius.definition_point,
Dimension::Diameter(diameter) => diameter.angle_vertex,
Dimension::LargeRadial(radial) => radial.chord_point,
_ => return None,
};
let association = document.objects.values().find_map(|object| {
@ -655,6 +656,11 @@ impl Scene {
diameter.measurement() * 0.5,
diameter.angle_vertex,
)),
Dimension::LargeRadial(radial) => Some((
radial.definition_point,
radial.measurement(),
radial.chord_point,
)),
_ => None,
};
if let Some((center, measured_radius, chord)) = radial_data {
@ -810,6 +816,11 @@ impl Scene {
diameter.measurement() * 0.5,
diameter.angle_vertex,
)),
Dimension::LargeRadial(radial) => Some((
radial.definition_point,
radial.measurement(),
radial.chord_point,
)),
_ => None,
};
if let Some((center, radius, chord)) = radial_data {
@ -1003,6 +1014,33 @@ impl Scene {
}
diameter.base.actual_measurement = diameter.measurement();
}
Dimension::LargeRadial(radial) => {
let Some((source, angle)) = radial_source else {
continue;
};
let old_center = radial.definition_point;
let new_center = source.center_world();
let new_chord = source.point_at_angle(angle);
let center_delta = Vector3::new(
new_center.x - old_center.x,
new_center.y - old_center.y,
new_center.z - old_center.z,
);
radial.definition_point = new_center;
radial.base.definition_point = new_center;
radial.chord_point = new_chord;
if point_distance_squared(center_delta, Vector3::default()) > 1e-18 {
radial.override_center = radial.override_center + center_delta;
radial.jog_point = radial.jog_point + center_delta;
if radial.base.text_user_positioned {
radial.base.text_middle_point =
radial.base.text_middle_point + center_delta;
radial.base.insertion_point =
radial.base.insertion_point + center_delta;
}
}
radial.base.actual_measurement = radial.measurement();
}
Dimension::Ordinate(ordinate) => {
if let Some(feature) = resolved[0] {
ordinate.feature_location = feature;
@ -1098,7 +1136,6 @@ impl Scene {
}
arc.base.actual_measurement = arc.measurement();
}
_ => continue,
}
dimension.base_mut().actual_measurement = dimension.measurement();
refreshed.push((association.dimension, ChangeKind::Modified));