Files
OneCAD-Draft/src/modules/draw/modify/mledit.rs
T

1134 lines
40 KiB
Rust

use acadrust::entities::MLine;
use acadrust::objects::MLineStyle;
use acadrust::{EntityType, Handle};
use cadkernel::geom2d::{Curve, Line};
use cadkernel::space::{PlanarCurve, Plane, Vec3 as KernelVec3};
use glam::DVec3;
use rustc_hash::FxHashMap as HashMap;
use crate::command::{CadCommand, CmdOption, CmdResult};
#[derive(Clone)]
pub struct MlineEditTarget {
pub entity: MLine,
pub style: MLineStyle,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Tool {
ClosedCross,
OpenCross,
MergedCross,
ClosedTee,
OpenTee,
MergedTee,
CornerJoint,
AddVertex,
DeleteVertex,
CutSingle,
CutAll,
WeldAll,
}
enum Mode {
Choose,
PickFirst(Tool),
PickSecond {
tool: Tool,
first: Handle,
first_pick: DVec3,
},
PickRangeEnd {
tool: Tool,
target: Handle,
start: DVec3,
},
}
pub struct MlineEditCommand {
targets: HashMap<u64, MlineEditTarget>,
mode: Mode,
}
impl MlineEditCommand {
pub fn new(targets: HashMap<u64, MlineEditTarget>) -> Self {
Self {
targets,
mode: Mode::Choose,
}
}
fn target(&self, handle: Handle) -> Option<&MlineEditTarget> {
self.targets.get(&handle.value())
}
fn replace(handle: Handle, mline: MLine) -> CmdResult {
CmdResult::ReplaceMany(
vec![(handle, vec![EntityType::MLine(mline)])],
Vec::new(),
)
}
fn edit_vertex(&self, tool: Tool, handle: Handle, point: DVec3) -> Option<CmdResult> {
let target = self.target(handle)?;
let mut mline = target.entity.clone();
match tool {
Tool::AddVertex => {
let (segment, fraction, projected, _) = closest_segment(&mline, point)?;
if !(1.0e-9..1.0 - 1.0e-9).contains(&fraction) {
return None;
}
let normal = DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z);
let source: Vec<_> = (0..mline.style_element_count)
.filter_map(|element| {
let (start, end) = element_segment(&mline, segment, element)?;
let data = mline.vertices[segment].segments.get(element)?.clone();
let curve = segment_curve(start, end, normal)?;
let length = curve.length();
let split = curve.parameter_at(projected.to_array())?.clamp(0.0, 1.0)
* length;
Some((data, length, split))
})
.collect();
if source.len() != mline.style_element_count {
return None;
}
let insert = segment + 1;
let mut vertex = mline.vertices[segment].clone();
vertex.position = acadrust::types::Vector3::new(
projected.x,
projected.y,
projected.z,
);
mline.vertices.insert(insert, vertex);
crate::entities::mline::rebuild_mline_geometry(&mut mline);
crate::modules::draw::draw::mline::sync_mline_element_parameters(
&mut mline,
&target.style,
);
let lengths: Vec<_> = (0..mline.style_element_count)
.filter_map(|element| {
let first = element_segment(&mline, segment, element)?;
let second = element_segment(&mline, insert, element)?;
Some((
segment_curve(first.0, first.1, normal)?.length(),
segment_curve(second.0, second.1, normal)?.length(),
))
})
.collect();
if lengths.len() != mline.style_element_count {
return None;
}
for (element, ((source, source_length, split), (first_length, second_length))) in
source.into_iter().zip(lengths).enumerate()
{
let first_drawn = remap_ranges(
&drawn_ranges(&source.parameters, source_length),
0.0,
split,
first_length,
);
let second_drawn = remap_ranges(
&drawn_ranges(&source.parameters, source_length),
split,
source_length,
second_length,
);
let first_cuts = remap_ranges(
&cut_ranges(&source.area_fill_parameters, source_length),
0.0,
split,
first_length,
);
let second_cuts = remap_ranges(
&cut_ranges(&source.area_fill_parameters, source_length),
split,
source_length,
second_length,
);
let (before, after) = mline.vertices.split_at_mut(insert);
store_drawn_ranges(
&mut before[segment].segments[element].parameters,
first_length,
&first_drawn,
);
store_cut_ranges(
&mut before[segment].segments[element].area_fill_parameters,
first_length,
&first_cuts,
);
store_drawn_ranges(
&mut after[0].segments[element].parameters,
second_length,
&second_drawn,
);
store_cut_ranges(
&mut after[0].segments[element].area_fill_parameters,
second_length,
&second_cuts,
);
}
}
Tool::DeleteVertex => {
let vertex = closest_vertex(&mline, point)?;
if mline.vertices.len() <= 2
|| (mline.is_closed() && mline.vertices.len() <= 3)
{
return None;
}
let count = mline.vertices.len();
let merge = mline.is_closed() || (vertex > 0 && vertex + 1 < count);
let previous = if vertex == 0 { count - 1 } else { vertex - 1 };
let normal = DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z);
let source = merge.then(|| {
(0..mline.style_element_count)
.filter_map(|element| {
let first = element_segment(&mline, previous, element)?;
let second = element_segment(&mline, vertex, element)?;
Some((
mline.vertices[previous].segments.get(element)?.clone(),
segment_curve(first.0, first.1, normal)?.length(),
mline.vertices[vertex].segments.get(element)?.clone(),
segment_curve(second.0, second.1, normal)?.length(),
))
})
.collect::<Vec<_>>()
});
if source
.as_ref()
.is_some_and(|source| source.len() != mline.style_element_count)
{
return None;
}
let candidates = if mline.is_closed() {
vec![
((previous + count - 1) % count, false),
((vertex + 1) % count, true),
]
} else {
let mut candidates = Vec::new();
if vertex >= 2 {
candidates.push((vertex - 2, false));
}
if vertex + 2 < count {
candidates.push((vertex + 1, true));
}
candidates
};
let mut peripheral = Vec::new();
for (segment, moved_start) in candidates {
if (merge && (segment == previous || segment == vertex))
|| peripheral
.iter()
.any(|(existing, _, _)| *existing == segment)
{
continue;
}
let snapshot = snapshot_segment_data(&mline, segment, normal)?;
let target = if segment > vertex {
segment - 1
} else {
segment
};
peripheral.push((target, moved_start, snapshot));
}
mline.vertices.remove(vertex);
crate::entities::mline::rebuild_mline_geometry(&mut mline);
crate::modules::draw::draw::mline::sync_mline_element_parameters(
&mut mline,
&target.style,
);
for (segment, moved_start, snapshot) in peripheral {
restore_shifted_segment_data(
&mut mline,
segment,
normal,
snapshot,
moved_start,
);
}
if let Some(source) = source {
let previous = if vertex == 0 {
mline.vertices.len() - 1
} else {
vertex - 1
};
for (element, (first, first_length, second, second_length)) in
source.into_iter().enumerate()
{
let segment = element_segment(&mline, previous, element)?;
let target_length = segment_curve(segment.0, segment.1, normal)?.length();
let total = first_length + second_length;
let mut drawn = drawn_ranges(&first.parameters, first_length);
drawn.extend(
drawn_ranges(&second.parameters, second_length)
.into_iter()
.map(|range| {
(range.0 + first_length, range.1 + first_length)
}),
);
let mut cuts = cut_ranges(&first.area_fill_parameters, first_length);
cuts.extend(
cut_ranges(&second.area_fill_parameters, second_length)
.into_iter()
.map(|range| {
(range.0 + first_length, range.1 + first_length)
}),
);
let drawn = merge_ranges(remap_ranges(
&drawn,
0.0,
total,
target_length,
));
let cuts = merge_ranges(remap_ranges(
&cuts,
0.0,
total,
target_length,
));
let target = &mut mline.vertices[previous].segments[element];
store_drawn_ranges(&mut target.parameters, target_length, &drawn);
store_cut_ranges(
&mut target.area_fill_parameters,
target_length,
&cuts,
);
}
}
}
_ => return None,
}
crate::modules::draw::draw::mline::sync_mline_element_parameters(
&mut mline,
&target.style,
);
Some(Self::replace(handle, mline))
}
fn edit_range(
&self,
tool: Tool,
handle: Handle,
start: DVec3,
end: DVec3,
) -> Option<CmdResult> {
let target = self.target(handle)?;
let mut mline = target.entity.clone();
let (segment, _, _, _) = closest_segment(&mline, start)?;
let elements: Vec<usize> = match tool {
Tool::CutSingle => vec![closest_element(&mline, segment, start)?],
_ => (0..mline.style_element_count).collect(),
};
for element in elements {
let (a, b) = element_segment(&mline, segment, element)?;
let curve = segment_curve(
a,
b,
DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z),
)?;
let length = curve.length();
if length <= 1.0e-10 {
continue;
}
let first = curve.parameter_at(start.to_array())?.clamp(0.0, 1.0) * length;
let second = curve.parameter_at(end.to_array())?.clamp(0.0, 1.0) * length;
let low = first.min(second);
let high = first.max(second);
let segment = &mut mline.vertices[segment].segments[element];
if tool == Tool::WeldAll {
add_drawn_range(&mut segment.parameters, length, low, high);
remove_cut_range(&mut segment.area_fill_parameters, length, low, high);
} else {
remove_drawn_range(&mut segment.parameters, length, low, high);
add_cut_range(&mut segment.area_fill_parameters, length, low, high);
}
}
Some(Self::replace(handle, mline))
}
fn edit_pair(
&self,
tool: Tool,
first_handle: Handle,
first_pick: DVec3,
second_handle: Handle,
second_pick: DVec3,
) -> Option<CmdResult> {
if first_handle == second_handle {
return None;
}
let first_target = self.target(first_handle)?;
let second_target = self.target(second_handle)?;
let mut first = first_target.entity.clone();
let mut second = second_target.entity.clone();
let (first_segment, _, _, _) = closest_segment(&first, first_pick)?;
let (second_segment, _, _, _) = closest_segment(&second, second_pick)?;
let first_end = if matches!(
tool,
Tool::ClosedTee | Tool::OpenTee | Tool::MergedTee | Tool::CornerJoint
) {
Some(selected_terminal_end(&first, first_segment, first_pick)?)
} else {
None
};
let second_end = if tool == Tool::CornerJoint {
Some(selected_terminal_end(&second, second_segment, second_pick)?)
} else {
None
};
let (intersection, first_fraction, second_fraction, sine) = segment_intersection(
center_segment(&first, first_segment)?,
center_segment(&second, second_segment)?,
DVec3::new(first.normal.x, first.normal.y, first.normal.z),
)?;
let on_segment = |value: f64| (-1.0e-6..=1.0 + 1.0e-6).contains(&value);
let valid = match tool {
Tool::ClosedCross | Tool::OpenCross | Tool::MergedCross => {
on_segment(first_fraction) && on_segment(second_fraction)
}
Tool::ClosedTee | Tool::OpenTee | Tool::MergedTee => {
on_segment(second_fraction)
}
Tool::CornerJoint => true,
_ => false,
};
if !valid || sine.abs() <= 1.0e-6 {
return None;
}
let divisor = sine.abs();
let first_gap = style_width(&second_target.style, second.scale_factor) * 0.5 / divisor;
let second_gap = style_width(&first_target.style, first.scale_factor) * 0.5 / divisor;
match tool {
Tool::ClosedCross => {
gap_elements(
&mut first,
first_segment,
intersection,
first_gap,
None,
);
}
Tool::OpenCross => {
gap_elements(
&mut first,
first_segment,
intersection,
first_gap,
None,
);
gap_elements(
&mut second,
second_segment,
intersection,
second_gap,
Some(outer_element_indices(&second_target.style)),
);
}
Tool::MergedCross => {
gap_elements(
&mut first,
first_segment,
intersection,
first_gap,
Some(inner_element_indices(&first_target.style)),
);
gap_elements(
&mut second,
second_segment,
intersection,
second_gap,
Some(inner_element_indices(&second_target.style)),
);
}
Tool::ClosedTee | Tool::OpenTee | Tool::MergedTee => {
move_closest_end(
&mut first,
&first_target.style,
first_end?,
intersection,
);
let elements = match tool {
Tool::ClosedTee => None,
Tool::OpenTee => Some(outer_element_indices(&second_target.style)),
Tool::MergedTee => Some(inner_element_indices(&second_target.style)),
_ => unreachable!(),
};
gap_elements(
&mut second,
second_segment,
intersection,
second_gap,
elements,
);
}
Tool::CornerJoint => {
move_closest_end(
&mut first,
&first_target.style,
first_end?,
intersection,
);
move_closest_end(
&mut second,
&second_target.style,
second_end?,
intersection,
);
}
_ => return None,
}
Some(CmdResult::ReplaceMany(
vec![
(first_handle, vec![EntityType::MLine(first)]),
(second_handle, vec![EntityType::MLine(second)]),
],
Vec::new(),
))
}
}
impl CadCommand for MlineEditCommand {
fn name(&self) -> &'static str {
"MLEDIT"
}
fn prompt(&self) -> String {
match self.mode {
Mode::Choose => crate::t!("MLEDIT Choose an edit tool:").into_owned(),
Mode::PickFirst(_) => crate::t!("MLEDIT Select first multiline:").into_owned(),
Mode::PickSecond { .. } => {
crate::t!("MLEDIT Select second multiline:").into_owned()
}
Mode::PickRangeEnd { tool: Tool::WeldAll, .. } => {
crate::t!("MLEDIT Specify the end of the weld range:").into_owned()
}
Mode::PickRangeEnd { .. } => {
crate::t!("MLEDIT Specify the second cut point:").into_owned()
}
}
}
fn options(&self) -> Vec<CmdOption> {
if !matches!(self.mode, Mode::Choose) {
return Vec::new();
}
vec![
CmdOption::new(crate::t!("Closed Cross").as_ref(), "CC"),
CmdOption::new(crate::t!("Open Cross").as_ref(), "OC"),
CmdOption::new(crate::t!("Merged Cross").as_ref(), "MC"),
CmdOption::new(crate::t!("Closed Tee").as_ref(), "CT"),
CmdOption::new(crate::t!("Open Tee").as_ref(), "OT"),
CmdOption::new(crate::t!("Merged Tee").as_ref(), "MT"),
CmdOption::new(crate::t!("Corner Joint").as_ref(), "CJ"),
CmdOption::new(crate::t!("Add Vertex").as_ref(), "AV"),
CmdOption::new(crate::t!("Delete Vertex").as_ref(), "DV"),
CmdOption::new(crate::t!("Cut Single").as_ref(), "CS"),
CmdOption::new(crate::t!("Cut All").as_ref(), "CA"),
CmdOption::new(crate::t!("Weld All").as_ref(), "WA"),
]
}
fn wants_text_input(&self) -> bool {
matches!(self.mode, Mode::Choose)
}
fn needs_entity_pick(&self) -> bool {
matches!(self.mode, Mode::PickFirst(_) | Mode::PickSecond { .. })
}
fn entity_pick_highlights_hover(&self) -> bool {
self.needs_entity_pick()
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
if !matches!(self.mode, Mode::Choose) {
return None;
}
let tool = match text.trim().to_uppercase().as_str() {
"CC" | "CLOSED CROSS" => Tool::ClosedCross,
"OC" | "OPEN CROSS" => Tool::OpenCross,
"MC" | "MERGED CROSS" => Tool::MergedCross,
"CT" | "CLOSED TEE" => Tool::ClosedTee,
"OT" | "OPEN TEE" => Tool::OpenTee,
"MT" | "MERGED TEE" => Tool::MergedTee,
"CJ" | "CORNER JOINT" => Tool::CornerJoint,
"AV" | "ADD VERTEX" => Tool::AddVertex,
"DV" | "DELETE VERTEX" => Tool::DeleteVertex,
"CS" | "CUT SINGLE" => Tool::CutSingle,
"CA" | "CUT ALL" => Tool::CutAll,
"WA" | "WELD ALL" => Tool::WeldAll,
_ => return None,
};
self.mode = Mode::PickFirst(tool);
Some(CmdResult::NeedPoint)
}
fn on_entity_pick(&mut self, handle: Handle, point: DVec3) -> CmdResult {
if self.target(handle).is_none() {
return CmdResult::NeedPoint;
}
match self.mode {
Mode::PickFirst(tool @ (Tool::AddVertex | Tool::DeleteVertex)) => self
.edit_vertex(tool, handle, point)
.unwrap_or(CmdResult::NeedPoint),
Mode::PickFirst(tool @ (Tool::CutSingle | Tool::CutAll | Tool::WeldAll)) => {
self.mode = Mode::PickRangeEnd {
tool,
target: handle,
start: point,
};
CmdResult::NeedPoint
}
Mode::PickFirst(tool) => {
self.mode = Mode::PickSecond {
tool,
first: handle,
first_pick: point,
};
CmdResult::NeedPoint
}
Mode::PickSecond {
tool,
first,
first_pick,
} => self
.edit_pair(tool, first, first_pick, handle, point)
.unwrap_or(CmdResult::NeedPoint),
_ => CmdResult::NeedPoint,
}
}
fn on_point(&mut self, point: DVec3) -> CmdResult {
match self.mode {
Mode::PickRangeEnd {
tool,
target,
start,
} => self
.edit_range(tool, target, start, point)
.unwrap_or(CmdResult::NeedPoint),
_ => CmdResult::NeedPoint,
}
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Cancel
}
}
fn center_segment(mline: &MLine, index: usize) -> Option<(DVec3, DVec3)> {
let first = mline.vertices.get(index)?.position;
let next = if index + 1 < mline.vertices.len() {
index + 1
} else if mline.is_closed() {
0
} else {
return None;
};
let second = mline.vertices[next].position;
Some((
DVec3::new(first.x, first.y, first.z),
DVec3::new(second.x, second.y, second.z),
))
}
fn segment_curve(first: DVec3, second: DVec3, normal: DVec3) -> Option<PlanarCurve> {
let direction = KernelVec3::from(second.to_array()) - KernelVec3::from(first.to_array());
let length = direction.length();
let plane = Plane::orthonormal(
first.to_array(),
direction.normalize()?.to_array(),
normal.to_array(),
)?;
Some(PlanarCurve::new(
plane,
Curve::Line(Line {
start: [0.0, 0.0],
end: [length, 0.0],
}),
))
}
fn closest_segment(mline: &MLine, point: DVec3) -> Option<(usize, f64, DVec3, f64)> {
let count = if mline.is_closed() {
mline.vertices.len()
} else {
mline.vertices.len().saturating_sub(1)
};
let normal = DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z);
(0..count)
.filter_map(|index| {
let (a, b) = center_segment(mline, index)?;
let curve = segment_curve(a, b, normal)?;
let fraction = curve.parameter_at(point.to_array())?.clamp(0.0, 1.0);
let projected = DVec3::from_array(curve.point_at(fraction));
let distance = KernelVec3::from(projected.to_array())
.distance_squared(KernelVec3::from(point.to_array()));
Some((index, fraction, projected, distance))
})
.min_by(|left, right| left.3.total_cmp(&right.3))
}
fn closest_vertex(mline: &MLine, point: DVec3) -> Option<usize> {
mline
.vertices
.iter()
.enumerate()
.min_by(|(_, left), (_, right)| {
let point = KernelVec3::from(point.to_array());
let left = KernelVec3::new(left.position.x, left.position.y, left.position.z)
.distance_squared(point);
let right = KernelVec3::new(right.position.x, right.position.y, right.position.z)
.distance_squared(point);
left.total_cmp(&right)
})
.map(|(index, _)| index)
}
fn element_segment(mline: &MLine, index: usize, element: usize) -> Option<(DVec3, DVec3)> {
let next = if index + 1 < mline.vertices.len() {
index + 1
} else if mline.is_closed() {
0
} else {
return None;
};
let point = |vertex: usize| -> Option<DVec3> {
let item = mline.vertices.get(vertex)?;
let offset = item.segments.get(element)?.parameters.first().copied()?;
let position = KernelVec3::new(item.position.x, item.position.y, item.position.z);
let miter = KernelVec3::new(item.miter.x, item.miter.y, item.miter.z);
Some(DVec3::from_array((position + miter * offset).to_array()))
};
Some((point(index)?, point(next)?))
}
fn snapshot_segment_data(
mline: &MLine,
segment: usize,
normal: DVec3,
) -> Option<Vec<(acadrust::entities::MLineSegment, f64)>> {
(0..mline.style_element_count)
.map(|element| {
let endpoints = element_segment(mline, segment, element)?;
Some((
mline.vertices.get(segment)?.segments.get(element)?.clone(),
segment_curve(endpoints.0, endpoints.1, normal)?.length(),
))
})
.collect()
}
fn restore_shifted_segment_data(
mline: &mut MLine,
segment: usize,
normal: DVec3,
source: Vec<(acadrust::entities::MLineSegment, f64)>,
moved_start: bool,
) {
for (element, (source, source_length)) in source.into_iter().enumerate() {
let Some(endpoints) = element_segment(mline, segment, element) else {
continue;
};
let Some(target_length) = segment_curve(endpoints.0, endpoints.1, normal)
.map(|curve| curve.length())
else {
continue;
};
let shift = if moved_start {
target_length - source_length
} else {
0.0
};
let drawn = shift_ranges(
&drawn_ranges(&source.parameters, source_length),
source_length,
shift,
target_length,
);
let cuts = shift_ranges(
&cut_ranges(&source.area_fill_parameters, source_length),
source_length,
shift,
target_length,
);
let Some(target) = mline
.vertices
.get_mut(segment)
.and_then(|vertex| vertex.segments.get_mut(element))
else {
continue;
};
store_drawn_ranges(&mut target.parameters, target_length, &drawn);
store_cut_ranges(&mut target.area_fill_parameters, target_length, &cuts);
}
}
fn closest_element(mline: &MLine, segment: usize, point: DVec3) -> Option<usize> {
let normal = DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z);
(0..mline.style_element_count)
.filter_map(|element| {
let (a, b) = element_segment(mline, segment, element)?;
let curve = segment_curve(a, b, normal)?;
let fraction = curve.parameter_at(point.to_array())?.clamp(0.0, 1.0);
let projected = KernelVec3::from(curve.point_at(fraction));
Some((
element,
projected.distance_squared(KernelVec3::from(point.to_array())),
))
})
.min_by(|left, right| left.1.total_cmp(&right.1))
.map(|(element, _)| element)
}
fn drawn_ranges(parameters: &[f64], length: f64) -> Vec<(f64, f64)> {
crate::entities::mline::mline_drawn_ranges(parameters, length)
}
fn store_drawn_ranges(parameters: &mut Vec<f64>, length: f64, ranges: &[(f64, f64)]) {
crate::entities::mline::store_mline_drawn_ranges(parameters, length, ranges);
}
fn remove_drawn_range(parameters: &mut Vec<f64>, length: f64, low: f64, high: f64) {
if high - low <= 1.0e-9 {
return;
}
let mut result = Vec::new();
for (start, end) in drawn_ranges(parameters, length) {
if low > start + 1.0e-9 {
result.push((start, low.min(end)));
}
if high < end - 1.0e-9 {
result.push((high.max(start), end));
}
}
store_drawn_ranges(parameters, length, &result);
}
fn add_drawn_range(parameters: &mut Vec<f64>, length: f64, low: f64, high: f64) {
let mut ranges = drawn_ranges(parameters, length);
ranges.push((low, high));
ranges.sort_by(|left, right| left.0.total_cmp(&right.0));
let mut merged: Vec<(f64, f64)> = Vec::new();
for range in ranges {
if let Some(last) = merged.last_mut() {
if range.0 <= last.1 + 1.0e-9 {
last.1 = last.1.max(range.1);
continue;
}
}
merged.push(range);
}
store_drawn_ranges(parameters, length, &merged);
}
fn cut_ranges(parameters: &[f64], length: f64) -> Vec<(f64, f64)> {
parameters
.chunks(2)
.filter_map(|pair| {
let start = pair[0].clamp(0.0, length);
let end = pair.get(1).copied().unwrap_or(length).clamp(0.0, length);
(end - start > 1.0e-9).then_some((start, end))
})
.collect()
}
fn store_cut_ranges(parameters: &mut Vec<f64>, length: f64, ranges: &[(f64, f64)]) {
parameters.clear();
for (start, end) in ranges {
parameters.push(*start);
if *end < length - 1.0e-9 {
parameters.push(*end);
}
}
}
fn add_cut_range(parameters: &mut Vec<f64>, length: f64, low: f64, high: f64) {
if high - low <= 1.0e-9 {
return;
}
let mut ranges = cut_ranges(parameters, length);
ranges.push((low, high));
ranges.sort_by(|left, right| left.0.total_cmp(&right.0));
let mut merged: Vec<(f64, f64)> = Vec::new();
for range in ranges {
if let Some(last) = merged.last_mut() {
if range.0 <= last.1 + 1.0e-9 {
last.1 = last.1.max(range.1);
continue;
}
}
merged.push(range);
}
store_cut_ranges(parameters, length, &merged);
}
fn remove_cut_range(parameters: &mut Vec<f64>, length: f64, low: f64, high: f64) {
if high - low <= 1.0e-9 {
return;
}
let mut result = Vec::new();
for (start, end) in cut_ranges(parameters, length) {
if low > start + 1.0e-9 {
result.push((start, low.min(end)));
}
if high < end - 1.0e-9 {
result.push((high.max(start), end));
}
}
store_cut_ranges(parameters, length, &result);
}
fn remap_ranges(
ranges: &[(f64, f64)],
source_start: f64,
source_end: f64,
target_length: f64,
) -> Vec<(f64, f64)> {
let source_length = source_end - source_start;
if source_length <= 1.0e-12 || target_length <= 1.0e-12 {
return Vec::new();
}
ranges
.iter()
.filter_map(|range| {
let start = range.0.max(source_start);
let end = range.1.min(source_end);
(end - start > 1.0e-9).then_some((
(start - source_start) / source_length * target_length,
(end - source_start) / source_length * target_length,
))
})
.collect()
}
fn merge_ranges(mut ranges: Vec<(f64, f64)>) -> Vec<(f64, f64)> {
ranges.sort_by(|left, right| left.0.total_cmp(&right.0));
let mut merged: Vec<(f64, f64)> = Vec::new();
for range in ranges {
if let Some(last) = merged.last_mut() {
if range.0 <= last.1 + 1.0e-9 {
last.1 = last.1.max(range.1);
continue;
}
}
merged.push(range);
}
merged
}
fn shift_ranges(
ranges: &[(f64, f64)],
source_length: f64,
shift: f64,
target_length: f64,
) -> Vec<(f64, f64)> {
ranges
.iter()
.filter_map(|range| {
let start = if range.0 <= 1.0e-9 {
0.0
} else {
(range.0 + shift).clamp(0.0, target_length)
};
let end = if range.1 >= source_length - 1.0e-9 {
target_length
} else {
(range.1 + shift).clamp(0.0, target_length)
};
(end - start > 1.0e-9).then_some((start, end))
})
.collect()
}
fn style_width(style: &MLineStyle, scale: f64) -> f64 {
let low = style
.elements
.iter()
.map(|element| element.offset)
.fold(f64::INFINITY, f64::min);
let high = style
.elements
.iter()
.map(|element| element.offset)
.fold(f64::NEG_INFINITY, f64::max);
if low.is_finite() && high.is_finite() {
(high - low).abs() * scale.abs()
} else {
scale.abs()
}
}
fn outer_element_indices(style: &MLineStyle) -> Vec<usize> {
if style.elements.is_empty() {
return Vec::new();
}
let low = style
.elements
.iter()
.enumerate()
.min_by(|(_, left), (_, right)| left.offset.total_cmp(&right.offset))
.map(|(index, _)| index)
.unwrap_or(0);
let high = style
.elements
.iter()
.enumerate()
.max_by(|(_, left), (_, right)| left.offset.total_cmp(&right.offset))
.map(|(index, _)| index)
.unwrap_or(low);
if low == high {
vec![low]
} else {
vec![low, high]
}
}
fn inner_element_indices(style: &MLineStyle) -> Vec<usize> {
let outer = outer_element_indices(style);
let inner: Vec<usize> = (0..style.elements.len())
.filter(|index| !outer.contains(index))
.collect();
if inner.is_empty() {
outer.into_iter().take(1).collect()
} else {
inner
}
}
fn gap_elements(
mline: &mut MLine,
segment: usize,
intersection: DVec3,
half_gap: f64,
elements: Option<Vec<usize>>,
) {
let elements = elements.unwrap_or_else(|| (0..mline.style_element_count).collect());
for element in elements {
let Some((a, b)) = element_segment(mline, segment, element) else {
continue;
};
let Some(curve) = segment_curve(
a,
b,
DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z),
) else {
continue;
};
let length = curve.length();
if let Some(element) = mline.vertices[segment].segments.get_mut(element) {
let Some(fraction) = curve.parameter_at(intersection.to_array()) else {
continue;
};
let at = fraction.clamp(0.0, 1.0) * length;
let low = (at - half_gap).max(0.0);
let high = (at + half_gap).min(length);
remove_drawn_range(&mut element.parameters, length, low, high);
add_cut_range(&mut element.area_fill_parameters, length, low, high);
}
}
}
fn move_closest_end(
mline: &mut MLine,
style: &MLineStyle,
index: usize,
intersection: DVec3,
) {
if mline.vertices.is_empty() || mline.is_closed() || index >= mline.vertices.len() {
return;
}
let last_index = mline.vertices.len() - 1;
if index != 0 && index != last_index {
return;
}
let segment = if index == 0 { 0 } else { last_index - 1 };
let normal = DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z);
let Some(source) = snapshot_segment_data(mline, segment, normal) else {
return;
};
let peripheral_segment = if index == 0 {
(mline.vertices.len() > 2).then_some(1)
} else {
(mline.vertices.len() > 2).then_some(last_index - 2)
};
let peripheral = peripheral_segment
.and_then(|segment| snapshot_segment_data(mline, segment, normal).map(|data| (segment, data)));
mline.vertices[index].position = acadrust::types::Vector3::new(
intersection.x,
intersection.y,
intersection.z,
);
crate::entities::mline::rebuild_mline_geometry(mline);
crate::modules::draw::draw::mline::sync_mline_element_parameters(mline, style);
restore_shifted_segment_data(mline, segment, normal, source, index == 0);
if let Some((segment, source)) = peripheral {
restore_shifted_segment_data(mline, segment, normal, source, index == 0);
}
}
fn selected_terminal_end(mline: &MLine, segment: usize, pick: DVec3) -> Option<usize> {
if mline.is_closed() || mline.vertices.len() < 2 {
return None;
}
let last_vertex = mline.vertices.len() - 1;
let last_segment = last_vertex - 1;
match (segment == 0, segment == last_segment) {
(true, true) => {
let pick = KernelVec3::from(pick.to_array());
let first = &mline.vertices[0].position;
let last = &mline.vertices[last_vertex].position;
let first_distance = KernelVec3::new(first.x, first.y, first.z).distance_squared(pick);
let last_distance = KernelVec3::new(last.x, last.y, last.z).distance_squared(pick);
Some(if first_distance <= last_distance { 0 } else { last_vertex })
}
(true, false) => Some(0),
(false, true) => Some(last_vertex),
(false, false) => None,
}
}
fn segment_intersection(
first: (DVec3, DVec3),
second: (DVec3, DVec3),
normal: DVec3,
) -> Option<(DVec3, f64, f64, f64)> {
let axis = (cadkernel::space::Vec3::from(first.1.to_array())
- cadkernel::space::Vec3::from(first.0.to_array()))
.normalize()?
.to_array();
let plane = cadkernel::space::Plane::orthonormal(
first.0.to_array(),
axis,
normal.to_array(),
)?;
let points = [
first.0.to_array(),
first.1.to_array(),
second.0.to_array(),
second.1.to_array(),
];
let tolerance = cadkernel::space::coplanarity_tolerance(&points);
if !plane.contains(points[2], tolerance) || !plane.contains(points[3], tolerance) {
return None;
}
let p = cadkernel::geom2d::Vec2::from(plane.project(points[0])?);
let q = cadkernel::geom2d::Vec2::from(plane.project(points[2])?);
let r = cadkernel::geom2d::Vec2::from(plane.project(points[1])?) - p;
let s = cadkernel::geom2d::Vec2::from(plane.project(points[3])?) - q;
let (t, u) = cadkernel::geom2d::intersect::line_line(
p.to_array(),
r.to_array(),
q.to_array(),
s.to_array(),
)?;
let intersection = plane.point_at((p + r * t).to_array());
let sine = r.cross(s) / (r.length() * s.length()).max(f64::MIN_POSITIVE);
Some((DVec3::from_array(intersection), t, u, sine))
}
inventory::submit!(crate::command::CommandRegistration { names: &["MLEDIT"] });