forked from 1b-agent/opencadstudio-upstream
fix(annotate): align annotation ribbon workflows
This commit is contained in:
1 parent
fc1788df65
commit
6505b27818
26 files changed
+1921
-345
No files matched your search
+595
-23
@@ -2777,6 +2777,33 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.refresh_properties();
|
||||
}
|
||||
CmdResult::UpdateEntityAndFinish { handle, entity } => {
|
||||
if self.reject_locked_edit(i, handle) {
|
||||
self.tabs[i].active_cmd = None;
|
||||
return Task::none();
|
||||
}
|
||||
let label = self.history_label_from_active_cmd(i, "EDIT");
|
||||
self.push_undo_snapshot(i, label);
|
||||
let is_dimension = matches!(entity, acadrust::EntityType::Dimension(_));
|
||||
if let Some(current) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
*current = entity;
|
||||
if is_dimension {
|
||||
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;
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("The selected object is no longer available.").as_ref(),
|
||||
);
|
||||
}
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.refresh_properties();
|
||||
}
|
||||
CmdResult::AddSketchConstraint { kind, refs, driving_param, label } => {
|
||||
if let Err(message) = self.tabs[i]
|
||||
.scene
|
||||
@@ -2948,36 +2975,111 @@ impl OpenCADStudio {
|
||||
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__")
|
||||
|| layer.starts_with("__DIMBREAK_AUTO__")
|
||||
{
|
||||
// DIMBREAK needs a break-gap field on the dimension
|
||||
// model (not yet present) to store and render the gap.
|
||||
// Report honestly rather than claiming success while
|
||||
// changing nothing. (#181 / DIM-020)
|
||||
self.command_line
|
||||
.push_info(crate::t!("DIMBREAK: not yet implemented — nothing changed.").as_ref());
|
||||
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.push_undo_snapshot(i, "DIMSPACE");
|
||||
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__") {
|
||||
// DIMJOGLINE needs a jog-point field on the dimension
|
||||
// model (not yet present) to store and render the jog.
|
||||
// Report honestly rather than faking success. (DIM-019)
|
||||
self.command_line
|
||||
.push_info(crate::t!("DIMJOGLINE: not yet implemented — nothing changed.").as_ref());
|
||||
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();
|
||||
@@ -5621,6 +5723,72 @@ impl OpenCADStudio {
|
||||
self.tabs[i].snap_result = None;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
}
|
||||
CmdResult::EditDimensions {
|
||||
mut handles,
|
||||
operation,
|
||||
} => {
|
||||
handles.retain(|handle| {
|
||||
!self.tabs[i].scene.is_layer_locked(*handle)
|
||||
&& matches!(
|
||||
self.tabs[i].scene.document.get_entity(*handle),
|
||||
Some(acadrust::EntityType::Dimension(_))
|
||||
)
|
||||
});
|
||||
if handles.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("DIMEDIT: no editable dimensions were selected.").as_ref());
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "DIMEDIT");
|
||||
for handle in &handles {
|
||||
if let Some(acadrust::EntityType::Dimension(dimension)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||
{
|
||||
use crate::command::DimensionEditOperation as Operation;
|
||||
match &operation {
|
||||
Operation::Home => {
|
||||
let base = dimension.base_mut();
|
||||
base.text_middle_point = acadrust::types::Vector3::ZERO;
|
||||
base.insertion_point = acadrust::types::Vector3::ZERO;
|
||||
base.text_user_positioned = false;
|
||||
base.text_rotation = 0.0;
|
||||
}
|
||||
Operation::NewText(text) => {
|
||||
dimension.base_mut().set_text_override(
|
||||
(!text.is_empty()).then_some(text.clone()),
|
||||
);
|
||||
}
|
||||
Operation::Rotate(degrees) => {
|
||||
dimension.base_mut().text_rotation = degrees.to_radians();
|
||||
}
|
||||
Operation::Oblique(degrees) => match dimension {
|
||||
acadrust::entities::Dimension::Linear(value) => {
|
||||
value.ext_line_rotation = degrees.to_radians();
|
||||
}
|
||||
acadrust::entities::Dimension::Aligned(value) => {
|
||||
value.ext_line_rotation = degrees.to_radians();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.invalidate_dim_block_recorded(*handle);
|
||||
}
|
||||
let changes: Vec<_> = handles
|
||||
.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(
|
||||
crate::tf!("DIMEDIT: updated {} dimension(s).", handles.len()).as_ref(),
|
||||
);
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.restore_pre_cmd_tangent();
|
||||
}
|
||||
CmdResult::DdeditEntity { handle, new_text } => {
|
||||
if self.reject_locked_edit(i, handle) {
|
||||
self.tabs[i].active_cmd = None;
|
||||
@@ -6176,6 +6344,382 @@ fn remap_object(
|
||||
|
||||
// ── DIMSPACE helper ───────────────────────────────────────────────────────────
|
||||
|
||||
struct DimBreakResult {
|
||||
changed: Vec<Handle>,
|
||||
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)> {
|
||||
let mut output = Vec::new();
|
||||
for model in models {
|
||||
for run in model.points.split(|point| !point[0].is_finite()) {
|
||||
for pair in run.windows(2) {
|
||||
let first = glam::DVec3::new(
|
||||
pair[0][0] as f64,
|
||||
pair[0][1] as f64,
|
||||
pair[0][2] as f64,
|
||||
);
|
||||
let second = glam::DVec3::new(
|
||||
pair[1][0] as f64,
|
||||
pair[1][1] as f64,
|
||||
pair[1][2] as f64,
|
||||
);
|
||||
if first.is_finite()
|
||||
&& second.is_finite()
|
||||
&& first.distance_squared(second) > 1.0e-18
|
||||
{
|
||||
output.push((first, second));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
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,
|
||||
))
|
||||
}
|
||||
|
||||
fn break_object_handle(document: &acadrust::CadDocument, dimension: Handle) -> Option<Handle> {
|
||||
document.objects.iter().find_map(|(handle, object)| {
|
||||
let acadrust::objects::ObjectType::DataObject(object) = object else {
|
||||
return None;
|
||||
};
|
||||
let acadrust::objects::DataObjectData::BreakData(data) = &object.data else {
|
||||
return None;
|
||||
};
|
||||
(data.dimension_reference == dimension).then_some(*handle)
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_dimension_break_data(document: &mut acadrust::CadDocument, dimension: Handle) -> bool {
|
||||
let handles: Vec<_> = document
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|(handle, object)| match object {
|
||||
acadrust::objects::ObjectType::DataObject(object) => match &object.data {
|
||||
acadrust::objects::DataObjectData::BreakData(data)
|
||||
if data.dimension_reference == dimension => Some(*handle),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
return false;
|
||||
}
|
||||
for handle in &handles {
|
||||
document.objects.remove(handle);
|
||||
}
|
||||
if let Some(dictionary_handle) = document.extension_dictionary_handle(dimension) {
|
||||
if let Some(acadrust::objects::ObjectType::Dictionary(dictionary)) =
|
||||
document.objects.get_mut(&dictionary_handle)
|
||||
{
|
||||
dictionary.entries.retain(|(name, handle)| {
|
||||
!name.eq_ignore_ascii_case("ACAD_BREAKDATA") && !handles.contains(handle)
|
||||
});
|
||||
dictionary
|
||||
.hard_owner_entries
|
||||
.retain(|name| !name.eq_ignore_ascii_case("ACAD_BREAKDATA"));
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn write_dimension_break_data(
|
||||
document: &mut acadrust::CadDocument,
|
||||
dimension: Handle,
|
||||
reserved_reference: Handle,
|
||||
references: Vec<acadrust::objects::BreakPointReference>,
|
||||
) -> bool {
|
||||
use acadrust::objects::{BreakData, DataObject, DataObjectData, Dictionary, ObjectType};
|
||||
|
||||
if references.is_empty() {
|
||||
return remove_dimension_break_data(document, dimension);
|
||||
}
|
||||
if let Some(handle) = break_object_handle(document, dimension) {
|
||||
if let Some(ObjectType::DataObject(object)) = document.objects.get_mut(&handle) {
|
||||
object.data = DataObjectData::BreakData(BreakData {
|
||||
version: 0,
|
||||
dimension_reference: dimension,
|
||||
reserved_reference,
|
||||
point_references: references,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let dictionary_handle = match document.extension_dictionary_handle(dimension) {
|
||||
Some(handle) => handle,
|
||||
None => {
|
||||
let handle = document.allocate_handle();
|
||||
let mut dictionary = Dictionary::new();
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = dimension;
|
||||
dictionary.hard_owner = true;
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(entity) = document.get_entity_mut(dimension) {
|
||||
entity.common_mut().xdictionary_handle = Some(handle);
|
||||
}
|
||||
handle
|
||||
}
|
||||
};
|
||||
|
||||
let object_handle = document.allocate_handle();
|
||||
let mut object = DataObject::new(DataObjectData::BreakData(BreakData {
|
||||
version: 0,
|
||||
dimension_reference: dimension,
|
||||
reserved_reference,
|
||||
point_references: references,
|
||||
}));
|
||||
object.handle = object_handle;
|
||||
object.owner = dictionary_handle;
|
||||
document
|
||||
.objects
|
||||
.insert(object_handle, ObjectType::DataObject(object));
|
||||
if let Some(ObjectType::Dictionary(dictionary)) =
|
||||
document.objects.get_mut(&dictionary_handle)
|
||||
{
|
||||
dictionary
|
||||
.entries
|
||||
.retain(|(name, _)| !name.eq_ignore_ascii_case("ACAD_BREAKDATA"));
|
||||
dictionary.add_entry("ACAD_BREAKDATA", object_handle);
|
||||
dictionary.set_entry_hard_owner("ACAD_BREAKDATA", true);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn break_reference(
|
||||
identifier: i32,
|
||||
reference_type: i32,
|
||||
first: glam::DVec3,
|
||||
second: glam::DVec3,
|
||||
) -> acadrust::objects::BreakPointReference {
|
||||
acadrust::objects::BreakPointReference {
|
||||
version: 0,
|
||||
reserved: 0,
|
||||
reference_type,
|
||||
flags: 0,
|
||||
identifier,
|
||||
first_point: acadrust::types::Vector3::new(first.x, first.y, first.z),
|
||||
second_point: acadrust::types::Vector3::new(second.x, second.y, second.z),
|
||||
trailing_version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
};
|
||||
let dimensions: Vec<_> = requested
|
||||
.into_iter()
|
||||
.filter(|handle| {
|
||||
matches!(
|
||||
scene.document.get_entity(*handle),
|
||||
Some(acadrust::EntityType::Dimension(_))
|
||||
) && scene.locked_layer_name(*handle).is_none()
|
||||
})
|
||||
.collect();
|
||||
if dimensions.is_empty() {
|
||||
return DimBreakResult {
|
||||
changed: Vec::new(),
|
||||
message: crate::t!("DIMBREAK: no editable dimensions were selected.").into_owned(),
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(operation, DimBreakOperation::Remove) {
|
||||
let changed: Vec<_> = dimensions
|
||||
.into_iter()
|
||||
.filter(|handle| remove_dimension_break_data(&mut scene.document, *handle))
|
||||
.collect();
|
||||
return DimBreakResult {
|
||||
message: crate::tf!("DIMBREAK: removed breaks from {} dimension(s).", changed.len())
|
||||
.into_owned(),
|
||||
changed,
|
||||
};
|
||||
}
|
||||
|
||||
let crossing_handles: Vec<Handle> = match operation {
|
||||
DimBreakOperation::Object(handle) => vec![handle],
|
||||
DimBreakOperation::Auto => scene
|
||||
.document
|
||||
.entities()
|
||||
.map(|entity| entity.common().handle)
|
||||
.filter(|handle| {
|
||||
!dimensions.contains(handle) && scene.entity_belongs_to_active_space(*handle)
|
||||
})
|
||||
.collect(),
|
||||
DimBreakOperation::Manual(_, _) | DimBreakOperation::Remove => Vec::new(),
|
||||
};
|
||||
let crossing_segments: Vec<_> = crossing_handles
|
||||
.iter()
|
||||
.flat_map(|handle| wire_segments(&scene.wire_models_for(&[*handle])))
|
||||
.collect();
|
||||
|
||||
let mut pending = Vec::new();
|
||||
for dimension in &dimensions {
|
||||
let existing: Vec<_> = break_object_handle(&scene.document, *dimension)
|
||||
.and_then(|handle| scene.document.objects.get(&handle))
|
||||
.and_then(|object| match object {
|
||||
acadrust::objects::ObjectType::DataObject(object) => match &object.data {
|
||||
acadrust::objects::DataObjectData::BreakData(data) => {
|
||||
Some(data.point_references.clone())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut references = if matches!(operation, DimBreakOperation::Manual(_, _)) {
|
||||
existing
|
||||
} else {
|
||||
existing
|
||||
.into_iter()
|
||||
.filter(|reference| reference.reference_type == 2)
|
||||
.collect()
|
||||
};
|
||||
match operation {
|
||||
DimBreakOperation::Manual(first, second) => {
|
||||
references.push(break_reference(references.len() as i32, 2, first, second));
|
||||
}
|
||||
DimBreakOperation::Auto | DimBreakOperation::Object(_) => {
|
||||
let dimension_segments = wire_segments(&scene.wire_models_for(&[*dimension]));
|
||||
let mut intersections = Vec::new();
|
||||
for dim_segment in &dimension_segments {
|
||||
for crossing_segment in &crossing_segments {
|
||||
if let Some(points) =
|
||||
segment_intersection_xy(*dim_segment, *crossing_segment)
|
||||
{
|
||||
let center = (points.0 + points.1) * 0.5;
|
||||
if intersections.iter().all(
|
||||
|(first, second): &(glam::DVec3, glam::DVec3)| {
|
||||
center.distance_squared((*first + *second) * 0.5) > 1.0e-8
|
||||
},
|
||||
) {
|
||||
intersections.push(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let start = references.len() as i32;
|
||||
references.extend(intersections.into_iter().enumerate().map(
|
||||
|(index, points)| {
|
||||
break_reference(start + index as i32, 1, points.0, points.1)
|
||||
},
|
||||
));
|
||||
}
|
||||
DimBreakOperation::Remove => {}
|
||||
}
|
||||
pending.push((*dimension, references));
|
||||
}
|
||||
|
||||
let reserved = match operation {
|
||||
DimBreakOperation::Object(handle) => handle,
|
||||
_ => Handle::NULL,
|
||||
};
|
||||
let changed: Vec<_> = pending
|
||||
.into_iter()
|
||||
.filter_map(|(dimension, references)| {
|
||||
write_dimension_break_data(&mut scene.document, dimension, reserved, references)
|
||||
.then_some(dimension)
|
||||
})
|
||||
.collect();
|
||||
DimBreakResult {
|
||||
message: if changed.is_empty() {
|
||||
crate::t!("DIMBREAK: no intersections were found.").into_owned()
|
||||
} else {
|
||||
crate::tf!("DIMBREAK: updated {} dimension(s).", changed.len()).into_owned()
|
||||
},
|
||||
changed,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>"
|
||||
@@ -6188,7 +6732,11 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
||||
.split(';')
|
||||
.filter_map(|s| s.parse::<u64>().ok())
|
||||
.collect();
|
||||
let spacing: f64 = parts[2].parse().unwrap_or(0.0);
|
||||
let requested_spacing = if parts[2].eq_ignore_ascii_case("AUTO") {
|
||||
None
|
||||
} else {
|
||||
parts[2].parse::<f64>().ok()
|
||||
};
|
||||
|
||||
use acadrust::entities::Dimension;
|
||||
let base_h = acadrust::Handle::from(base_val);
|
||||
@@ -6196,25 +6744,27 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
||||
// 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) = match scene.document.get_entity(base_h) {
|
||||
Some(acadrust::EntityType::Dimension(Dimension::Linear(d))) => {
|
||||
let (perp, base_coord, 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;
|
||||
(perp, dp.x * perp.0 + dp.y * perp.1)
|
||||
let spacing = dimension_auto_spacing(&scene.document, dimension, scene.creation_annotation_multiplier());
|
||||
(perp, dp.x * perp.0 + dp.y * perp.1, spacing)
|
||||
}
|
||||
Some(acadrust::EntityType::Dimension(Dimension::Aligned(d))) => {
|
||||
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;
|
||||
(perp, dp.x * perp.0 + dp.y * perp.1)
|
||||
let spacing = dimension_auto_spacing(&scene.document, dimension, scene.creation_annotation_multiplier());
|
||||
(perp, dp.x * perp.0 + dp.y * perp.1, spacing)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let effective_spacing = if spacing <= 0.0 { 10.0 } else { spacing };
|
||||
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);
|
||||
@@ -6256,6 +6806,28 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn dimension_auto_spacing(
|
||||
document: &acadrust::CadDocument,
|
||||
dimension: &acadrust::entities::Dimension,
|
||||
annotation_multiplier: f64,
|
||||
) -> f64 {
|
||||
let style = document
|
||||
.dim_styles
|
||||
.iter()
|
||||
.find(|style| style.name.eq_ignore_ascii_case(&dimension.base().style_name))
|
||||
.map(|style| crate::entities::dimension::resolved_dimension_style(style, dimension, document));
|
||||
style
|
||||
.map(|style| {
|
||||
let scale = if style.dimscale > 1.0e-9 {
|
||||
style.dimscale
|
||||
} else {
|
||||
annotation_multiplier.max(1.0e-9)
|
||||
};
|
||||
(style.dimdli.abs() * scale).max(1.0e-6)
|
||||
})
|
||||
.unwrap_or(3.75)
|
||||
}
|
||||
|
||||
fn apply_mleader_align(
|
||||
scene: &mut crate::scene::Scene,
|
||||
handles: &[acadrust::Handle],
|
||||
|
||||
Reference in new issue
Block a user