forked from 1b-agent/opencadstudio-upstream
fix: resolve annotation pull request findings
This commit is contained in:
1 parent
293d0796fb
commit
6c0badbdc8
10 files changed
+407
-425
No files matched your search
Generated
+2
-2
@@ -919,7 +919,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=1d81d7f9470b843feee06fb9136997d4a69ac553#1d81d7f9470b843feee06fb9136997d4a69ac553"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=50f020377c01fcebb5f31844b9900f046b05192f#50f020377c01fcebb5f31844b9900f046b05192f"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
@@ -930,7 +930,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cadkernel-constraints"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=1d81d7f9470b843feee06fb9136997d4a69ac553#1d81d7f9470b843feee06fb9136997d4a69ac553"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=50f020377c01fcebb5f31844b9900f046b05192f#50f020377c01fcebb5f31844b9900f046b05192f"
|
||||
dependencies = [
|
||||
"nalgebra",
|
||||
]
|
||||
|
||||
+2
-2
@@ -33,8 +33,8 @@ rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "4d7b359b0571f9b6e0f85a62a21c67508722b83e", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "1d81d7f9470b843feee06fb9136997d4a69ac553", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "1d81d7f9470b843feee06fb9136997d4a69ac553" }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "50f020377c01fcebb5f31844b9900f046b05192f", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "50f020377c01fcebb5f31844b9900f046b05192f" }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -1396,3 +1396,53 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cell_ranges_are_normalized_and_missing_cells_are_blank() {
|
||||
let rows = vec![
|
||||
vec!["a".into(), "b".into(), "c".into()],
|
||||
vec!["d".into()],
|
||||
];
|
||||
assert_eq!(
|
||||
crop_range(rows, "C2:A1").unwrap(),
|
||||
vec![
|
||||
vec!["a".to_string(), "b".to_string(), "c".to_string()],
|
||||
vec!["d".to_string(), String::new(), String::new()],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_range_targets_accept_quoted_sheet_names() {
|
||||
assert_eq!(
|
||||
named_range_target("='Sheet One'!$A$1:$D$10"),
|
||||
Some(("Sheet One".into(), "A1:D10".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csv_links_apply_the_selected_cell_range() {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"ocs-data-link-{}-{}.csv",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
std::fs::write(&path, "a,b,c\nd,e,f\n").unwrap();
|
||||
let rows = read_tabular_file(&path, None, LinkRange::CellRange, Some("B1:C2"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
assert_eq!(
|
||||
rows.unwrap(),
|
||||
vec![
|
||||
vec!["b".to_string(), "c".to_string()],
|
||||
vec!["e".to_string(), "f".to_string()],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+147
-255
@@ -2949,6 +2949,93 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.refresh_properties();
|
||||
}
|
||||
CmdResult::EditDimensionBreak {
|
||||
dimensions,
|
||||
operation,
|
||||
} => {
|
||||
self.push_undo_snapshot(i, "DIMBREAK");
|
||||
let result = apply_dimbreak(&mut self.tabs[i].scene, &dimensions, operation);
|
||||
if result.changed.is_empty() {
|
||||
self.command_line.push_info(&result.message);
|
||||
} else {
|
||||
for handle in &result.changed {
|
||||
self.tabs[i].scene.invalidate_dim_block_recorded(*handle);
|
||||
}
|
||||
let changes = result
|
||||
.changed
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect::<Vec<_>>();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&result.message);
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
}
|
||||
CmdResult::EditDimensionJog { dimension, point } => {
|
||||
let valid = matches!(
|
||||
self.tabs[i].scene.document.get_entity(dimension),
|
||||
Some(acadrust::EntityType::Dimension(
|
||||
acadrust::entities::Dimension::Linear(_)
|
||||
| acadrust::entities::Dimension::Aligned(_)
|
||||
))
|
||||
);
|
||||
if !valid {
|
||||
self.command_line.push_error(
|
||||
crate::t!("DIMJOGLINE: select a linear or aligned dimension.").as_ref(),
|
||||
);
|
||||
} else if !self.reject_locked_edit(i, dimension) {
|
||||
self.push_undo_snapshot(i, "DIMJOGLINE");
|
||||
let values = point.map(|point| {
|
||||
use acadrust::xdata::XDataValue;
|
||||
vec![
|
||||
XDataValue::Integer16(387),
|
||||
XDataValue::Integer16(3),
|
||||
XDataValue::Integer16(389),
|
||||
XDataValue::Point3D(acadrust::types::Vector3::new(
|
||||
point.x, point.y, point.z,
|
||||
)),
|
||||
]
|
||||
});
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
&mut self.tabs[i].scene.document,
|
||||
dimension,
|
||||
"ACAD_DSTYLE_DIMJAG_POSITION",
|
||||
values,
|
||||
);
|
||||
self.tabs[i].scene.invalidate_dim_block_recorded(dimension);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(dimension, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(
|
||||
if point.is_some() {
|
||||
crate::t!("DIMJOGLINE: jog added.")
|
||||
} else {
|
||||
crate::t!("DIMJOGLINE: jog removed.")
|
||||
}
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
}
|
||||
CmdResult::SpaceDimensions {
|
||||
base,
|
||||
others,
|
||||
spacing,
|
||||
} => {
|
||||
self.push_undo_snapshot(i, "DIMSPACE");
|
||||
if apply_dimspace(&mut self.tabs[i].scene, base, &others, spacing) {
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::t!("DIMSPACE Spacing adjusted.").as_ref());
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
}
|
||||
CmdResult::ReplaceEntity(handle, new_entities) => {
|
||||
if self.reject_locked_edit(i, handle) {
|
||||
return Task::none();
|
||||
@@ -2974,122 +3061,6 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Detect DIMBREAK sentinel.
|
||||
if new_entities.len() == 1 {
|
||||
if let acadrust::EntityType::XLine(ref xl) = new_entities[0] {
|
||||
let layer = xl.common.layer.clone();
|
||||
if layer.starts_with("__DIMBREAK_") {
|
||||
self.push_undo_snapshot(i, "DIMBREAK");
|
||||
let result = apply_dimbreak(&mut self.tabs[i].scene, &layer);
|
||||
if result.changed.is_empty() {
|
||||
self.command_line.push_info(&result.message);
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
for handle in &result.changed {
|
||||
self.tabs[i].scene.invalidate_dim_block_recorded(*handle);
|
||||
}
|
||||
let changes: Vec<_> = result
|
||||
.changed
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&result.message);
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
if layer.starts_with("__DIMSPACE__") {
|
||||
self.push_undo_snapshot(i, "DIMSPACE");
|
||||
if let Some(encoded) = layer.strip_prefix("__DIMSPACE__") {
|
||||
apply_dimspace(&mut self.tabs[i].scene, encoded);
|
||||
}
|
||||
self.command_line.push_output(crate::t!("DIMSPACE Spacing adjusted.").as_ref());
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
if layer.starts_with("__DIMJOG__")
|
||||
|| layer.starts_with("__DIMJOG_REMOVE__")
|
||||
{
|
||||
let remove = layer.starts_with("__DIMJOG_REMOVE__");
|
||||
let point = layer
|
||||
.strip_prefix("__DIMJOG__")
|
||||
.and_then(|encoded| encoded.split_once('|'))
|
||||
.and_then(|(_, coordinates)| {
|
||||
let mut values = coordinates
|
||||
.split(',')
|
||||
.filter_map(|value| value.parse::<f64>().ok());
|
||||
Some(acadrust::types::Vector3::new(
|
||||
values.next()?,
|
||||
values.next()?,
|
||||
values.next()?,
|
||||
))
|
||||
});
|
||||
let valid = matches!(
|
||||
self.tabs[i].scene.document.get_entity(handle),
|
||||
Some(acadrust::EntityType::Dimension(
|
||||
acadrust::entities::Dimension::Linear(_)
|
||||
| acadrust::entities::Dimension::Aligned(_)
|
||||
))
|
||||
);
|
||||
if !valid || (!remove && point.is_none()) {
|
||||
self.command_line.push_error(
|
||||
crate::t!("DIMJOGLINE: select a linear or aligned dimension.")
|
||||
.as_ref(),
|
||||
);
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
self.push_undo_snapshot(i, "DIMJOGLINE");
|
||||
if remove {
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
"ACAD_DSTYLE_DIMJAG_POSITION",
|
||||
None,
|
||||
);
|
||||
} else if let Some(point) = point {
|
||||
use acadrust::xdata::XDataValue;
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
"ACAD_DSTYLE_DIMJAG_POSITION",
|
||||
Some(vec![
|
||||
XDataValue::Integer16(387),
|
||||
XDataValue::Integer16(3),
|
||||
XDataValue::Integer16(389),
|
||||
XDataValue::Point3D(point),
|
||||
]),
|
||||
);
|
||||
}
|
||||
self.tabs[i].scene.invalidate_dim_block_recorded(handle);
|
||||
self.tabs[i].scene.bump_entities(&[(
|
||||
handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(
|
||||
if remove {
|
||||
crate::t!("DIMJOGLINE: jog removed.")
|
||||
} else {
|
||||
crate::t!("DIMJOGLINE: jog added.")
|
||||
}
|
||||
.as_ref(),
|
||||
);
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let label = self.history_label_from_active_cmd(i, "TRIM");
|
||||
self.push_undo_snapshot(i, label);
|
||||
self.tabs[i].scene.erase_entities(&[handle]);
|
||||
@@ -6352,56 +6323,6 @@ struct DimBreakResult {
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum DimBreakOperation {
|
||||
Auto,
|
||||
Object(Handle),
|
||||
Manual(glam::DVec3, glam::DVec3),
|
||||
Remove,
|
||||
}
|
||||
|
||||
fn parse_dim_break_handles(encoded: &str) -> Vec<Handle> {
|
||||
encoded
|
||||
.split(';')
|
||||
.filter_map(|value| value.parse::<u64>().ok())
|
||||
.map(Handle::from)
|
||||
.filter(|handle| !handle.is_null())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_dim_break_point(encoded: &str) -> Option<glam::DVec3> {
|
||||
let mut values = encoded.split(',').map(str::parse::<f64>);
|
||||
Some(glam::DVec3::new(
|
||||
values.next()?.ok()?,
|
||||
values.next()?.ok()?,
|
||||
values.next()?.ok()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_dim_break(encoded: &str) -> Option<(Vec<Handle>, DimBreakOperation)> {
|
||||
if let Some(handles) = encoded.strip_prefix("__DIMBREAK_AUTO__") {
|
||||
return Some((parse_dim_break_handles(handles), DimBreakOperation::Auto));
|
||||
}
|
||||
if let Some(handles) = encoded.strip_prefix("__DIMBREAK_REMOVE__") {
|
||||
return Some((parse_dim_break_handles(handles), DimBreakOperation::Remove));
|
||||
}
|
||||
if let Some(rest) = encoded.strip_prefix("__DIMBREAK_OBJECT__") {
|
||||
let (handles, crossing) = rest.split_once('|')?;
|
||||
return Some((
|
||||
parse_dim_break_handles(handles),
|
||||
DimBreakOperation::Object(Handle::from(crossing.parse::<u64>().ok()?)),
|
||||
));
|
||||
}
|
||||
if let Some(rest) = encoded.strip_prefix("__DIMBREAK_MANUAL__") {
|
||||
let mut parts = rest.split('|');
|
||||
let handles = parse_dim_break_handles(parts.next()?);
|
||||
let first = parse_dim_break_point(parts.next()?)?;
|
||||
let second = parse_dim_break_point(parts.next()?)?;
|
||||
return Some((handles, DimBreakOperation::Manual(first, second)));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn wire_segments(
|
||||
models: &[crate::scene::model::wire_model::WireModel],
|
||||
) -> Vec<(glam::DVec3, glam::DVec3)> {
|
||||
@@ -6435,33 +6356,12 @@ fn segment_intersection_xy(
|
||||
first: (glam::DVec3, glam::DVec3),
|
||||
second: (glam::DVec3, glam::DVec3),
|
||||
) -> Option<(glam::DVec3, glam::DVec3)> {
|
||||
let p = first.0;
|
||||
let r = first.1 - first.0;
|
||||
let q = second.0;
|
||||
let s = second.1 - second.0;
|
||||
let cross = |a: glam::DVec3, b: glam::DVec3| a.x * b.y - a.y * b.x;
|
||||
let denominator = cross(r, s);
|
||||
if denominator.abs() <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
let qp = q - p;
|
||||
let t = cross(qp, s) / denominator;
|
||||
let u = cross(qp, r) / denominator;
|
||||
if !(-1.0e-9..=1.0 + 1.0e-9).contains(&t)
|
||||
|| !(-1.0e-9..=1.0 + 1.0e-9).contains(&u)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let direction = r.normalize_or_zero();
|
||||
if direction.length_squared() <= 1.0e-18 {
|
||||
return None;
|
||||
}
|
||||
let center = p + r * t.clamp(0.0, 1.0);
|
||||
let half_gap = 0.25_f64.min(first.0.distance(first.1) * 0.2);
|
||||
Some((
|
||||
center - direction * half_gap,
|
||||
center + direction * half_gap,
|
||||
))
|
||||
let gap = cadkernel::space::segment_break_gap_xy(
|
||||
[first.0.to_array(), first.1.to_array()],
|
||||
[second.0.to_array(), second.1.to_array()],
|
||||
0.25,
|
||||
)?;
|
||||
Some((glam::DVec3::from_array(gap[0]), glam::DVec3::from_array(gap[1])))
|
||||
}
|
||||
|
||||
fn break_object_handle(document: &acadrust::CadDocument, dimension: Handle) -> Option<Handle> {
|
||||
@@ -6593,15 +6493,16 @@ fn break_reference(
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakResult {
|
||||
let Some((requested, operation)) = parse_dim_break(encoded) else {
|
||||
return DimBreakResult {
|
||||
changed: Vec::new(),
|
||||
message: crate::t!("DIMBREAK: invalid operation data.").into_owned(),
|
||||
};
|
||||
};
|
||||
fn apply_dimbreak(
|
||||
scene: &mut crate::scene::Scene,
|
||||
requested: &[Handle],
|
||||
operation: crate::command::DimensionBreakOperation,
|
||||
) -> DimBreakResult {
|
||||
use crate::command::DimensionBreakOperation;
|
||||
|
||||
let dimensions: Vec<_> = requested
|
||||
.into_iter()
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|handle| {
|
||||
matches!(
|
||||
scene.document.get_entity(*handle),
|
||||
@@ -6616,7 +6517,7 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(operation, DimBreakOperation::Remove) {
|
||||
if matches!(operation, DimensionBreakOperation::Remove) {
|
||||
let changed: Vec<_> = dimensions
|
||||
.into_iter()
|
||||
.filter(|handle| remove_dimension_break_data(&mut scene.document, *handle))
|
||||
@@ -6629,8 +6530,8 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
}
|
||||
|
||||
let crossing_handles: Vec<Handle> = match operation {
|
||||
DimBreakOperation::Object(handle) => vec![handle],
|
||||
DimBreakOperation::Auto => scene
|
||||
DimensionBreakOperation::Object(handle) => vec![handle],
|
||||
DimensionBreakOperation::Auto => scene
|
||||
.document
|
||||
.entities()
|
||||
.map(|entity| entity.common().handle)
|
||||
@@ -6638,7 +6539,7 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
!dimensions.contains(handle) && scene.entity_belongs_to_active_space(*handle)
|
||||
})
|
||||
.collect(),
|
||||
DimBreakOperation::Manual(_, _) | DimBreakOperation::Remove => Vec::new(),
|
||||
DimensionBreakOperation::Manual(_, _) | DimensionBreakOperation::Remove => Vec::new(),
|
||||
};
|
||||
let crossing_segments: Vec<_> = crossing_handles
|
||||
.iter()
|
||||
@@ -6659,7 +6560,7 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut references = if matches!(operation, DimBreakOperation::Manual(_, _)) {
|
||||
let mut references = if matches!(operation, DimensionBreakOperation::Manual(_, _)) {
|
||||
existing
|
||||
} else {
|
||||
existing
|
||||
@@ -6668,10 +6569,10 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
.collect()
|
||||
};
|
||||
match operation {
|
||||
DimBreakOperation::Manual(first, second) => {
|
||||
DimensionBreakOperation::Manual(first, second) => {
|
||||
references.push(break_reference(references.len() as i32, 2, first, second));
|
||||
}
|
||||
DimBreakOperation::Auto | DimBreakOperation::Object(_) => {
|
||||
DimensionBreakOperation::Auto | DimensionBreakOperation::Object(_) => {
|
||||
let dimension_segments = wire_segments(&scene.wire_models_for(&[*dimension]));
|
||||
let mut intersections = Vec::new();
|
||||
for dim_segment in &dimension_segments {
|
||||
@@ -6697,13 +6598,13 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
},
|
||||
));
|
||||
}
|
||||
DimBreakOperation::Remove => {}
|
||||
DimensionBreakOperation::Remove => {}
|
||||
}
|
||||
pending.push((*dimension, references));
|
||||
}
|
||||
|
||||
let reserved = match operation {
|
||||
DimBreakOperation::Object(handle) => handle,
|
||||
DimensionBreakOperation::Object(handle) => handle,
|
||||
_ => Handle::NULL,
|
||||
};
|
||||
let changed: Vec<_> = pending
|
||||
@@ -6723,65 +6624,55 @@ fn apply_dimbreak(scene: &mut crate::scene::Scene, encoded: &str) -> DimBreakRes
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `base_val,h1;h2;...;hN,spacing` and adjust parallel dimension positions.
|
||||
fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
||||
// Format: "<base_handle>,<h1>;<h2>;...;<hN>,<spacing>"
|
||||
let parts: Vec<&str> = encoded.splitn(3, ',').collect();
|
||||
if parts.len() < 3 {
|
||||
return;
|
||||
}
|
||||
let base_val: u64 = parts[0].parse().unwrap_or(0);
|
||||
let other_vals: Vec<u64> = parts[1]
|
||||
.split(';')
|
||||
.filter_map(|s| s.parse::<u64>().ok())
|
||||
.collect();
|
||||
let requested_spacing = if parts[2].eq_ignore_ascii_case("AUTO") {
|
||||
None
|
||||
} else {
|
||||
parts[2].parse::<f64>().ok()
|
||||
};
|
||||
|
||||
fn apply_dimspace(
|
||||
scene: &mut crate::scene::Scene,
|
||||
base_h: Handle,
|
||||
others: &[Handle],
|
||||
requested_spacing: Option<f64>,
|
||||
) -> bool {
|
||||
use acadrust::entities::Dimension;
|
||||
let base_h = acadrust::Handle::from(base_val);
|
||||
// Base dim: the perpendicular direction (from its rotation / axis) and the
|
||||
// dim line's perpendicular coordinate. Spacing steps each parallel dim along
|
||||
// this perp IN THE DRAWING PLANE — offsetting Z had no effect on the dim
|
||||
// line, which is computed from def·perp with perp.z = 0. (#181 / DIM-021)
|
||||
let (perp, base_coord, auto_spacing) = match scene.document.get_entity(base_h) {
|
||||
let (axis, normal, definition, auto_spacing) = match scene.document.get_entity(base_h) {
|
||||
Some(acadrust::EntityType::Dimension(dimension @ Dimension::Linear(d))) => {
|
||||
let (s, c) = d.rotation.sin_cos();
|
||||
let perp = (-s, c);
|
||||
let dp = d.definition_point;
|
||||
let spacing = dimension_auto_spacing(&scene.document, dimension, scene.creation_annotation_multiplier());
|
||||
(perp, dp.x * perp.0 + dp.y * perp.1, spacing)
|
||||
(
|
||||
[d.rotation.cos(), d.rotation.sin(), 0.0],
|
||||
[d.base.normal.x, d.base.normal.y, d.base.normal.z],
|
||||
[d.definition_point.x, d.definition_point.y, d.definition_point.z],
|
||||
spacing,
|
||||
)
|
||||
}
|
||||
Some(acadrust::EntityType::Dimension(dimension @ Dimension::Aligned(d))) => {
|
||||
let dx = d.second_point.x - d.first_point.x;
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-12);
|
||||
let perp = (-dy / len, dx / len);
|
||||
let dp = d.definition_point;
|
||||
let spacing = dimension_auto_spacing(&scene.document, dimension, scene.creation_annotation_multiplier());
|
||||
(perp, dp.x * perp.0 + dp.y * perp.1, spacing)
|
||||
(
|
||||
[
|
||||
d.second_point.x - d.first_point.x,
|
||||
d.second_point.y - d.first_point.y,
|
||||
d.second_point.z - d.first_point.z,
|
||||
],
|
||||
[d.base.normal.x, d.base.normal.y, d.base.normal.z],
|
||||
[d.definition_point.x, d.definition_point.y, d.definition_point.z],
|
||||
spacing,
|
||||
)
|
||||
}
|
||||
_ => return,
|
||||
_ => return false,
|
||||
};
|
||||
let Some(frame) = cadkernel::space::dimension_spacing_frame(axis, normal, definition) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let effective_spacing = requested_spacing.unwrap_or(auto_spacing);
|
||||
let mut changes = Vec::new();
|
||||
for (idx, &hv) in other_vals.iter().enumerate() {
|
||||
let h = acadrust::Handle::from(hv);
|
||||
let target = base_coord + effective_spacing * (idx + 1) as f64;
|
||||
for (idx, &h) in others.iter().enumerate() {
|
||||
let target = frame.coordinate + effective_spacing * (idx + 1) as f64;
|
||||
let mut changed = false;
|
||||
if let Some(acadrust::EntityType::Dimension(dim)) = scene.document.get_entity_mut(h) {
|
||||
// Slide this dim's definition point along perp so its perpendicular
|
||||
// coordinate equals `target`; update both the struct field (render)
|
||||
// and base (save).
|
||||
let slide = |p: &mut acadrust::types::Vector3| {
|
||||
let cur = p.x * perp.0 + p.y * perp.1;
|
||||
let delta = target - cur;
|
||||
p.x += perp.0 * delta;
|
||||
p.y += perp.1 * delta;
|
||||
let point = cadkernel::space::move_to_dimension_spacing(
|
||||
[p.x, p.y, p.z],
|
||||
frame,
|
||||
target,
|
||||
);
|
||||
*p = acadrust::types::Vector3::new(point[0], point[1], point[2]);
|
||||
};
|
||||
match dim {
|
||||
Dimension::Linear(d) => {
|
||||
@@ -6807,6 +6698,7 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
||||
if !changes.is_empty() {
|
||||
scene.bump_entities(&changes);
|
||||
}
|
||||
!changes.is_empty()
|
||||
}
|
||||
|
||||
fn dimension_auto_spacing(
|
||||
|
||||
@@ -1322,6 +1322,14 @@ pub enum DimensionEditOperation {
|
||||
Oblique(f64),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DimensionBreakOperation {
|
||||
Auto,
|
||||
Object(Handle),
|
||||
Manual(DVec3, DVec3),
|
||||
Remove,
|
||||
}
|
||||
|
||||
/// Returned by every `CadCommand` method to tell main.rs what to do.
|
||||
#[allow(dead_code)]
|
||||
pub enum CmdResult {
|
||||
@@ -1488,6 +1496,19 @@ pub enum CmdResult {
|
||||
source: Handle,
|
||||
point: DVec3,
|
||||
},
|
||||
EditDimensionBreak {
|
||||
dimensions: Vec<Handle>,
|
||||
operation: DimensionBreakOperation,
|
||||
},
|
||||
EditDimensionJog {
|
||||
dimension: Handle,
|
||||
point: Option<DVec3>,
|
||||
},
|
||||
SpaceDimensions {
|
||||
base: Handle,
|
||||
others: Vec<Handle>,
|
||||
spacing: Option<f64>,
|
||||
},
|
||||
/// Cancel: discard any preview and end the command.
|
||||
Cancel,
|
||||
/// End the command and begin in-place editing of the table cell under
|
||||
|
||||
+48
-43
@@ -3252,64 +3252,63 @@ fn dimension_jog_point(dimension: &Dimension) -> Option<Vec3> {
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_dimension_jog(lines: &mut Vec<[f32; 3]>, requested: Vec3, size: f32, angle: f32) {
|
||||
fn apply_dimension_jog(
|
||||
lines: &mut Vec<[f32; 3]>,
|
||||
requested: Vec3,
|
||||
normal: Vec3,
|
||||
size: f32,
|
||||
angle: f32,
|
||||
) {
|
||||
if lines.len() < 2 {
|
||||
return;
|
||||
}
|
||||
let mut best: Option<(usize, Vec3, f32)> = None;
|
||||
for (index, segment) in lines.windows(2).enumerate() {
|
||||
if segment[0][0].is_nan() || segment[1][0].is_nan() {
|
||||
continue;
|
||||
}
|
||||
let first = Vec3::from_array(segment[0]);
|
||||
let second = Vec3::from_array(segment[1]);
|
||||
let delta = second - first;
|
||||
let length_squared = delta.length_squared();
|
||||
if length_squared <= 1.0e-12 {
|
||||
continue;
|
||||
}
|
||||
let parameter = ((requested - first).dot(delta) / length_squared).clamp(0.0, 1.0);
|
||||
let point = first + delta * parameter;
|
||||
let distance = point.distance_squared(requested);
|
||||
if best.is_none_or(|(_, _, current)| distance < current) {
|
||||
best = Some((index, point, distance));
|
||||
}
|
||||
}
|
||||
let Some((target, center, _)) = best else {
|
||||
let segments = lines
|
||||
.windows(2)
|
||||
.enumerate()
|
||||
.filter(|(_, segment)| segment[0][0].is_finite() && segment[1][0].is_finite())
|
||||
.map(|(index, segment)| {
|
||||
(
|
||||
index,
|
||||
[segment[0].map(f64::from), segment[1].map(f64::from)],
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let coordinates = segments.iter().map(|(_, segment)| *segment).collect::<Vec<_>>();
|
||||
let Some((candidate, center)) = cadkernel::space::nearest_segment_point(
|
||||
&coordinates,
|
||||
requested.to_array().map(f64::from),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let first = Vec3::from_array(lines[target]);
|
||||
let second = Vec3::from_array(lines[target + 1]);
|
||||
let Some(direction) = (second - first).try_normalize() else {
|
||||
let target = segments[candidate].0;
|
||||
let Some(jog) = cadkernel::space::dimension_jog_points(
|
||||
coordinates[candidate],
|
||||
center,
|
||||
normal.to_array().map(f64::from),
|
||||
size as f64,
|
||||
angle as f64,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let perpendicular = Vec3::new(-direction.y, direction.x, 0.0);
|
||||
let half = size.max(1.0e-3).min(first.distance(second) * 0.2);
|
||||
let amplitude = (half * (angle * 0.5).tan().abs()).clamp(half * 0.35, half * 1.5);
|
||||
let jog_start = center - direction * half;
|
||||
let jog_end = center + direction * half;
|
||||
let jog = [
|
||||
jog_start,
|
||||
center - direction * half * 0.25 + perpendicular * amplitude,
|
||||
center + direction * half * 0.25 - perpendicular * amplitude,
|
||||
jog_end,
|
||||
];
|
||||
let jog = jog.map(|point| point.map(|value| value as f32));
|
||||
let first = lines[target];
|
||||
let second = lines[target + 1];
|
||||
|
||||
let mut output = Vec::with_capacity(lines.len() + 4);
|
||||
output.extend_from_slice(&lines[..target]);
|
||||
if !output.is_empty() && !output.last().is_some_and(|point| point[0].is_nan()) {
|
||||
output.push([f32::NAN; 3]);
|
||||
}
|
||||
if first.distance_squared(jog_start) > 1.0e-12 {
|
||||
output.push(first.to_array());
|
||||
output.push(jog_start.to_array());
|
||||
if first != jog[0] {
|
||||
output.push(first);
|
||||
output.push(jog[0]);
|
||||
output.push([f32::NAN; 3]);
|
||||
}
|
||||
output.extend(jog.into_iter().map(|point| point.to_array()));
|
||||
if jog_end.distance_squared(second) > 1.0e-12 {
|
||||
output.extend(jog);
|
||||
if jog[3] != second {
|
||||
output.push([f32::NAN; 3]);
|
||||
output.push(jog_end.to_array());
|
||||
output.push(second.to_array());
|
||||
output.push(jog[3]);
|
||||
output.push(second);
|
||||
}
|
||||
if target + 2 < lines.len() {
|
||||
if !output.last().is_some_and(|point| point[0].is_nan())
|
||||
@@ -3664,7 +3663,13 @@ fn tessellate_dimension_inner(
|
||||
let jog_angle = style
|
||||
.map(|style| style.dimjogang as f32)
|
||||
.unwrap_or(std::f32::consts::FRAC_PI_4);
|
||||
apply_dimension_jog(&mut geom.dim_lines, point, dim_txt as f32 * 0.6, jog_angle);
|
||||
apply_dimension_jog(
|
||||
&mut geom.dim_lines,
|
||||
point,
|
||||
vec3_local(dim.base().normal),
|
||||
dim_txt as f32 * 0.6,
|
||||
jog_angle,
|
||||
);
|
||||
}
|
||||
apply_dimension_breaks(document, handle, &mut geom.dim_lines);
|
||||
apply_dimension_breaks(document, handle, &mut geom.ext_lines);
|
||||
|
||||
@@ -1195,6 +1195,7 @@ fn move_segment_parallel(polyline: &mut LwPolyline, segment: usize, offset: f64)
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn resize_arc_concentrically(polyline: &mut LwPolyline, segment: usize, offset: f64) {
|
||||
edit_polyline_geometry(polyline, |geometry| {
|
||||
geometry
|
||||
|
||||
@@ -39,33 +39,18 @@ impl DimBreakCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn marker(layer: String, primary: Handle) -> CmdResult {
|
||||
use acadrust::entities::XLine;
|
||||
let mut marker = XLine::default();
|
||||
marker.common.layer = layer;
|
||||
CmdResult::ReplaceEntity(primary, vec![EntityType::XLine(marker)])
|
||||
}
|
||||
|
||||
fn handles(handles: &[Handle]) -> String {
|
||||
handles
|
||||
.iter()
|
||||
.map(|handle| handle.value().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(";")
|
||||
}
|
||||
|
||||
fn automatic(handles: &[Handle]) -> CmdResult {
|
||||
Self::marker(
|
||||
format!("__DIMBREAK_AUTO__{}", Self::handles(handles)),
|
||||
handles[0],
|
||||
)
|
||||
CmdResult::EditDimensionBreak {
|
||||
dimensions: handles.to_vec(),
|
||||
operation: crate::command::DimensionBreakOperation::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(handles: &[Handle]) -> CmdResult {
|
||||
Self::marker(
|
||||
format!("__DIMBREAK_REMOVE__{}", Self::handles(handles)),
|
||||
handles[0],
|
||||
)
|
||||
CmdResult::EditDimensionBreak {
|
||||
dimensions: handles.to_vec(),
|
||||
operation: crate::command::DimensionBreakOperation::Remove,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,14 +140,10 @@ impl CadCommand for DimBreakCommand {
|
||||
}
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::PickCrossing(handles) => Self::marker(
|
||||
format!(
|
||||
"__DIMBREAK_OBJECT__{}|{}",
|
||||
Self::handles(handles),
|
||||
handle.value()
|
||||
),
|
||||
handles[0],
|
||||
),
|
||||
Step::PickCrossing(handles) => CmdResult::EditDimensionBreak {
|
||||
dimensions: handles.clone(),
|
||||
operation: crate::command::DimensionBreakOperation::Object(handle),
|
||||
},
|
||||
Step::ManualFirst(_) | Step::ManualSecond(_, _) => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
@@ -194,19 +175,10 @@ impl CadCommand for DimBreakCommand {
|
||||
self.step = Step::ManualSecond(handles.clone(), point);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::ManualSecond(handles, first) => Self::marker(
|
||||
format!(
|
||||
"__DIMBREAK_MANUAL__{}|{:.15},{:.15},{:.15}|{:.15},{:.15},{:.15}",
|
||||
Self::handles(handles),
|
||||
first.x,
|
||||
first.y,
|
||||
first.z,
|
||||
point.x,
|
||||
point.y,
|
||||
point.z
|
||||
),
|
||||
handles[0],
|
||||
),
|
||||
Step::ManualSecond(handles, first) => CmdResult::EditDimensionBreak {
|
||||
dimensions: handles.clone(),
|
||||
operation: crate::command::DimensionBreakOperation::Manual(*first, point),
|
||||
},
|
||||
_ => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
@@ -224,3 +196,21 @@ impl CadCommand for DimBreakCommand {
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMBREAK"] });
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn automatic_break_returns_a_typed_edit() {
|
||||
let dimensions = vec![Handle::from(7), Handle::from(9)];
|
||||
let CmdResult::EditDimensionBreak {
|
||||
dimensions: actual,
|
||||
operation: crate::command::DimensionBreakOperation::Auto,
|
||||
} = DimBreakCommand::automatic(&dimensions)
|
||||
else {
|
||||
panic!("expected dimension break edit");
|
||||
};
|
||||
assert_eq!(actual, dimensions);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use acadrust::entities::Dimension;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::{DVec3, Vec3};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult, InputKind};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -43,16 +43,10 @@ impl DimJogLineCommand {
|
||||
}
|
||||
|
||||
fn result(handle: Handle, point: Option<DVec3>) -> CmdResult {
|
||||
use acadrust::entities::XLine;
|
||||
let mut marker = XLine::default();
|
||||
marker.common.layer = match point {
|
||||
Some(point) => format!(
|
||||
"__DIMJOG__{}|{:.15},{:.15},{:.15}",
|
||||
handle.value(), point.x, point.y, point.z
|
||||
),
|
||||
None => format!("__DIMJOG_REMOVE__{}", handle.value()),
|
||||
};
|
||||
CmdResult::ReplaceEntity(handle, vec![EntityType::XLine(marker)])
|
||||
CmdResult::EditDimensionJog {
|
||||
dimension: handle,
|
||||
point,
|
||||
}
|
||||
}
|
||||
|
||||
fn default_position(dimension: &Dimension) -> Option<DVec3> {
|
||||
@@ -75,29 +69,23 @@ impl DimJogLineCommand {
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let first = DVec3::new(first.x, first.y, first.z);
|
||||
let second = DVec3::new(second.x, second.y, second.z);
|
||||
let definition = DVec3::new(definition.x, definition.y, definition.z);
|
||||
let axis = axis.try_normalize().unwrap_or(DVec3::X);
|
||||
let perpendicular = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let offset = definition.dot(perpendicular);
|
||||
let first_on_line = first + perpendicular * (offset - first.dot(perpendicular));
|
||||
let second_on_line = second + perpendicular * (offset - second.dot(perpendicular));
|
||||
let midpoint = (first_on_line + second_on_line) * 0.5;
|
||||
|
||||
let text = dimension.base().text_middle_point;
|
||||
let text = DVec3::new(text.x, text.y, text.z);
|
||||
let line = second_on_line - first_on_line;
|
||||
let length_squared = line.length_squared();
|
||||
if length_squared <= 1.0e-18 {
|
||||
return Some(midpoint);
|
||||
}
|
||||
let parameter = (text - first_on_line).dot(line) / length_squared;
|
||||
if text.is_finite() && (0.0..=1.0).contains(¶meter) {
|
||||
Some((first_on_line + text) * 0.5)
|
||||
let base = dimension.base();
|
||||
let text = base.text_middle_point;
|
||||
let normal = base.normal;
|
||||
let text = if base.text_user_positioned {
|
||||
[text.x, text.y, text.z]
|
||||
} else {
|
||||
Some(midpoint)
|
||||
}
|
||||
[f64::NAN; 3]
|
||||
};
|
||||
cadkernel::space::default_dimension_jog_position(
|
||||
[first.x, first.y, first.z],
|
||||
[second.x, second.y, second.z],
|
||||
[definition.x, definition.y, definition.z],
|
||||
axis.to_array(),
|
||||
[normal.x, normal.y, normal.z],
|
||||
text,
|
||||
)
|
||||
.map(DVec3::from_array)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,34 +186,37 @@ impl CadCommand for DimJogLineCommand {
|
||||
if !matches!(self.step, Step::PickJogPos { .. }) {
|
||||
return None;
|
||||
}
|
||||
let axis = self
|
||||
.dimension
|
||||
.as_ref()
|
||||
.and_then(|dimension| match dimension {
|
||||
Dimension::Linear(value) => {
|
||||
Some(Vec3::new(value.rotation.cos() as f32, value.rotation.sin() as f32, 0.0))
|
||||
}
|
||||
Dimension::Aligned(value) => Some(Vec3::new(
|
||||
(value.second_point.x - value.first_point.x) as f32,
|
||||
(value.second_point.y - value.first_point.y) as f32,
|
||||
0.0,
|
||||
)),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(Vec3::try_normalize)
|
||||
.unwrap_or(Vec3::X);
|
||||
let perpendicular = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let center = point.as_vec3();
|
||||
let size = 0.3;
|
||||
let points = vec![
|
||||
center - axis * size,
|
||||
center - axis * size * 0.25 + perpendicular * size,
|
||||
center + axis * size * 0.25 - perpendicular * size,
|
||||
center + axis * size,
|
||||
];
|
||||
let dimension = self.dimension.as_ref()?;
|
||||
let (segment, normal) = match dimension {
|
||||
Dimension::Linear(value) => {
|
||||
let axis = DVec3::new(value.rotation.cos(), value.rotation.sin(), 0.0);
|
||||
(
|
||||
[(point - axis).to_array(), (point + axis).to_array()],
|
||||
[value.base.normal.x, value.base.normal.y, value.base.normal.z],
|
||||
)
|
||||
}
|
||||
Dimension::Aligned(value) => (
|
||||
[
|
||||
[value.first_point.x, value.first_point.y, value.first_point.z],
|
||||
[value.second_point.x, value.second_point.y, value.second_point.z],
|
||||
],
|
||||
[value.base.normal.x, value.base.normal.y, value.base.normal.z],
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
let points = cadkernel::space::dimension_jog_points(
|
||||
segment,
|
||||
point.to_array(),
|
||||
normal,
|
||||
0.3,
|
||||
std::f64::consts::FRAC_PI_2,
|
||||
)?;
|
||||
let mut preview = WireModel::default();
|
||||
preview.name = "dimjog_preview".into();
|
||||
preview.points = points.into_iter().map(|point| point.to_array()).collect();
|
||||
preview.points = points
|
||||
.into_iter()
|
||||
.map(|point| point.map(|value| value as f32))
|
||||
.collect();
|
||||
preview.color = WireModel::CYAN;
|
||||
preview.line_weight_px = 1.2;
|
||||
Some(preview)
|
||||
@@ -233,3 +224,22 @@ impl CadCommand for DimJogLineCommand {
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMJOGLINE"] });
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn jog_result_keeps_the_selected_point() {
|
||||
let point = DVec3::new(1.0, 2.0, 3.0);
|
||||
let CmdResult::EditDimensionJog {
|
||||
dimension,
|
||||
point: actual,
|
||||
} = DimJogLineCommand::result(Handle::from(7), Some(point))
|
||||
else {
|
||||
panic!("expected dimension jog edit");
|
||||
};
|
||||
assert_eq!(dimension, Handle::from(7));
|
||||
assert_eq!(actual, Some(point));
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ impl CadCommand for DimSpaceCommand {
|
||||
if let Step::EnterSpacing { base, others } = &self.step {
|
||||
let token = text.trim();
|
||||
let spacing = if matches!(token.to_ascii_uppercase().as_str(), "A" | "AUTO") {
|
||||
"AUTO".to_string()
|
||||
None
|
||||
} else {
|
||||
let Ok(value) = token.parse::<f64>() else {
|
||||
return Some(CmdResult::ReportError(
|
||||
@@ -176,24 +176,13 @@ impl CadCommand for DimSpaceCommand {
|
||||
t!("DIMSPACE: spacing value cannot be negative.").into_owned(),
|
||||
));
|
||||
}
|
||||
value.to_string()
|
||||
Some(value)
|
||||
};
|
||||
let b = *base;
|
||||
let o = others.clone();
|
||||
// Emit sentinel for commands.rs to handle
|
||||
use acadrust::entities::XLine;
|
||||
let mut xl = XLine::default();
|
||||
let handles_str: Vec<String> = o.iter().map(|h| h.value().to_string()).collect();
|
||||
xl.common.layer = format!(
|
||||
"__DIMSPACE__{},{},{}",
|
||||
b.value(),
|
||||
handles_str.join(";"),
|
||||
spacing
|
||||
);
|
||||
return Some(CmdResult::ReplaceEntity(
|
||||
b,
|
||||
vec![acadrust::EntityType::XLine(xl)],
|
||||
));
|
||||
return Some(CmdResult::SpaceDimensions {
|
||||
base: *base,
|
||||
others: others.clone(),
|
||||
spacing,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -211,7 +200,31 @@ impl CadCommand for DimSpaceCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
}
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMSPACE", "DSPACE"] });
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIMSPACE", "DSPACE"] }); // DimSpaceCommand
|
||||
#[test]
|
||||
fn auto_spacing_returns_a_typed_edit() {
|
||||
let mut command = DimSpaceCommand {
|
||||
step: Step::EnterSpacing {
|
||||
base: Handle::from(2),
|
||||
others: vec![Handle::from(3)],
|
||||
},
|
||||
picked_entity: None,
|
||||
};
|
||||
let Some(CmdResult::SpaceDimensions {
|
||||
base,
|
||||
others,
|
||||
spacing,
|
||||
}) = command.on_text_input("Auto")
|
||||
else {
|
||||
panic!("expected dimension spacing edit");
|
||||
};
|
||||
assert_eq!(base, Handle::from(2));
|
||||
assert_eq!(others, vec![Handle::from(3)]);
|
||||
assert_eq!(spacing, None);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user