forked from 1b-agent/opencadstudio-upstream
Add multiline editing workflows
This commit is contained in:
1 parent
2263168a2a
commit
fc047b3b38
3 files changed
+687
No files matched your search
@@ -0,0 +1,658 @@
|
||||
use acadrust::entities::MLine;
|
||||
use acadrust::objects::MLineStyle;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::{DVec2, 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, _, projected, _) = closest_segment(&mline, point)?;
|
||||
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);
|
||||
mline.rebuild_geometry();
|
||||
}
|
||||
Tool::DeleteVertex => {
|
||||
let vertex = closest_vertex(&mline, point)?;
|
||||
if !mline.remove_vertex(vertex) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => 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 direction = b - a;
|
||||
let length = direction.length();
|
||||
if length <= 1.0e-10 {
|
||||
continue;
|
||||
}
|
||||
let unit = direction / length;
|
||||
let first = (start - a).dot(unit).clamp(0.0, length);
|
||||
let second = (end - a).dot(unit).clamp(0.0, length);
|
||||
let low = first.min(second);
|
||||
let high = first.max(second);
|
||||
let parameters = &mut mline.vertices[segment].segments[element].parameters;
|
||||
if tool == Tool::WeldAll {
|
||||
add_drawn_range(parameters, length, low, high);
|
||||
} else {
|
||||
remove_drawn_range(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 (intersection, first_fraction, second_fraction, sine) = segment_intersection(
|
||||
center_segment(&first, first_segment)?,
|
||||
center_segment(&second, second_segment)?,
|
||||
)?;
|
||||
let first_length = center_segment(&first, first_segment)?.1.distance(
|
||||
center_segment(&first, first_segment)?.0,
|
||||
);
|
||||
let second_length = center_segment(&second, second_segment)?.1.distance(
|
||||
center_segment(&second, second_segment)?.0,
|
||||
);
|
||||
let first_at = first_fraction * first_length;
|
||||
let second_at = second_fraction * second_length;
|
||||
let divisor = sine.abs().max(0.15);
|
||||
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, first_at, first_gap, None);
|
||||
}
|
||||
Tool::OpenCross => {
|
||||
gap_elements(&mut first, first_segment, first_at, first_gap, None);
|
||||
gap_elements(
|
||||
&mut second,
|
||||
second_segment,
|
||||
second_at,
|
||||
second_gap,
|
||||
Some(outer_element_indices(&second_target.style)),
|
||||
);
|
||||
}
|
||||
Tool::MergedCross => {
|
||||
gap_elements(
|
||||
&mut first,
|
||||
first_segment,
|
||||
first_at,
|
||||
first_gap,
|
||||
Some(inner_element_indices(&first_target.style)),
|
||||
);
|
||||
gap_elements(
|
||||
&mut second,
|
||||
second_segment,
|
||||
second_at,
|
||||
second_gap,
|
||||
Some(inner_element_indices(&second_target.style)),
|
||||
);
|
||||
}
|
||||
Tool::ClosedTee | Tool::OpenTee | Tool::MergedTee => {
|
||||
move_closest_end(&mut first, first_pick, intersection);
|
||||
crate::modules::draw::draw::mline::sync_mline_element_parameters(
|
||||
&mut first,
|
||||
&first_target.style,
|
||||
);
|
||||
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,
|
||||
second_at,
|
||||
second_gap,
|
||||
elements,
|
||||
);
|
||||
}
|
||||
Tool::CornerJoint => {
|
||||
move_closest_end(&mut first, first_pick, intersection);
|
||||
move_closest_end(&mut second, second_pick, intersection);
|
||||
crate::modules::draw::draw::mline::sync_mline_element_parameters(
|
||||
&mut first,
|
||||
&first_target.style,
|
||||
);
|
||||
crate::modules::draw::draw::mline::sync_mline_element_parameters(
|
||||
&mut second,
|
||||
&second_target.style,
|
||||
);
|
||||
}
|
||||
_ => 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 => "MLEDIT Choose an edit tool:".to_string(),
|
||||
Mode::PickFirst(_) => "MLEDIT Select first multiline:".to_string(),
|
||||
Mode::PickSecond { .. } => "MLEDIT Select second multiline:".to_string(),
|
||||
Mode::PickRangeEnd { tool: Tool::WeldAll, .. } => {
|
||||
"MLEDIT Specify the end of the weld range:".to_string()
|
||||
}
|
||||
Mode::PickRangeEnd { .. } => {
|
||||
"MLEDIT Specify the second cut point:".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
if !matches!(self.mode, Mode::Choose) {
|
||||
return Vec::new();
|
||||
}
|
||||
vec![
|
||||
CmdOption::new("Closed Cross", "CC"),
|
||||
CmdOption::new("Open Cross", "OC"),
|
||||
CmdOption::new("Merged Cross", "MC"),
|
||||
CmdOption::new("Closed Tee", "CT"),
|
||||
CmdOption::new("Open Tee", "OT"),
|
||||
CmdOption::new("Merged Tee", "MT"),
|
||||
CmdOption::new("Corner Joint", "CJ"),
|
||||
CmdOption::new("Add Vertex", "AV"),
|
||||
CmdOption::new("Delete Vertex", "DV"),
|
||||
CmdOption::new("Cut Single", "CS"),
|
||||
CmdOption::new("Cut All", "CA"),
|
||||
CmdOption::new("Weld All", "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 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)
|
||||
};
|
||||
(0..count)
|
||||
.filter_map(|index| {
|
||||
let (a, b) = center_segment(mline, index)?;
|
||||
let delta = b - a;
|
||||
let length_squared = delta.length_squared();
|
||||
if length_squared <= 1.0e-20 {
|
||||
return None;
|
||||
}
|
||||
let fraction = ((point - a).dot(delta) / length_squared).clamp(0.0, 1.0);
|
||||
let projected = a + delta * fraction;
|
||||
Some((index, fraction, projected, projected.distance_squared(point)))
|
||||
})
|
||||
.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 left = DVec3::new(left.position.x, left.position.y, left.position.z)
|
||||
.distance_squared(point);
|
||||
let right = DVec3::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()?;
|
||||
Some(DVec3::new(
|
||||
item.position.x + item.miter.x * offset,
|
||||
item.position.y + item.miter.y * offset,
|
||||
item.position.z + item.miter.z * offset,
|
||||
))
|
||||
};
|
||||
Some((point(index)?, point(next)?))
|
||||
}
|
||||
|
||||
fn closest_element(mline: &MLine, segment: usize, point: DVec3) -> Option<usize> {
|
||||
(0..mline.style_element_count)
|
||||
.filter_map(|element| {
|
||||
let (a, b) = element_segment(mline, segment, element)?;
|
||||
let delta = b - a;
|
||||
let fraction = ((point - a).dot(delta) / delta.length_squared().max(1.0e-20))
|
||||
.clamp(0.0, 1.0);
|
||||
Some((element, (a + delta * fraction).distance_squared(point)))
|
||||
})
|
||||
.min_by(|left, right| left.1.total_cmp(&right.1))
|
||||
.map(|(element, _)| element)
|
||||
}
|
||||
|
||||
fn drawn_ranges(parameters: &[f64], length: f64) -> Vec<(f64, f64)> {
|
||||
if parameters.len() <= 1 {
|
||||
return vec![(0.0, length)];
|
||||
}
|
||||
let toggles = ¶meters[1..];
|
||||
let mut ranges = Vec::new();
|
||||
let mut index = 0;
|
||||
while index < toggles.len() {
|
||||
let start = toggles[index].clamp(0.0, length);
|
||||
let end = toggles
|
||||
.get(index + 1)
|
||||
.copied()
|
||||
.unwrap_or(length)
|
||||
.clamp(0.0, length);
|
||||
if end - start > 1.0e-9 {
|
||||
ranges.push((start, end));
|
||||
}
|
||||
index += 2;
|
||||
}
|
||||
ranges
|
||||
}
|
||||
|
||||
fn store_drawn_ranges(parameters: &mut Vec<f64>, length: f64, ranges: &[(f64, f64)]) {
|
||||
let offset = parameters.first().copied().unwrap_or(0.0);
|
||||
parameters.clear();
|
||||
parameters.push(offset);
|
||||
if ranges.len() == 1 && ranges[0].0 <= 1.0e-9 && ranges[0].1 >= length - 1.0e-9 {
|
||||
return;
|
||||
}
|
||||
for (start, end) in ranges {
|
||||
parameters.push(*start);
|
||||
if *end < length - 1.0e-9 {
|
||||
parameters.push(*end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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,
|
||||
at: f64,
|
||||
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 length = a.distance(b);
|
||||
if let Some(parameters) = mline.vertices[segment].segments.get_mut(element) {
|
||||
remove_drawn_range(
|
||||
&mut parameters.parameters,
|
||||
length,
|
||||
(at - half_gap).max(0.0),
|
||||
(at + half_gap).min(length),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_closest_end(mline: &mut MLine, pick: DVec3, intersection: DVec3) {
|
||||
if mline.vertices.is_empty() || mline.is_closed() {
|
||||
return;
|
||||
}
|
||||
let first = DVec3::new(
|
||||
mline.vertices[0].position.x,
|
||||
mline.vertices[0].position.y,
|
||||
mline.vertices[0].position.z,
|
||||
);
|
||||
let last_index = mline.vertices.len() - 1;
|
||||
let last = DVec3::new(
|
||||
mline.vertices[last_index].position.x,
|
||||
mline.vertices[last_index].position.y,
|
||||
mline.vertices[last_index].position.z,
|
||||
);
|
||||
let index = if first.distance_squared(pick) <= last.distance_squared(pick) {
|
||||
0
|
||||
} else {
|
||||
last_index
|
||||
};
|
||||
let _ = mline.set_vertex_position(
|
||||
index,
|
||||
acadrust::types::Vector3::new(intersection.x, intersection.y, intersection.z),
|
||||
);
|
||||
}
|
||||
|
||||
fn segment_intersection(
|
||||
first: (DVec3, DVec3),
|
||||
second: (DVec3, DVec3),
|
||||
) -> Option<(DVec3, f64, f64, f64)> {
|
||||
let p = first.0.truncate();
|
||||
let r = (first.1 - first.0).truncate();
|
||||
let q = second.0.truncate();
|
||||
let s = (second.1 - second.0).truncate();
|
||||
let cross = |left: DVec2, right: DVec2| left.x * right.y - left.y * right.x;
|
||||
let denominator = cross(r, s);
|
||||
if denominator.abs() <= 1.0e-10 {
|
||||
return None;
|
||||
}
|
||||
let t = cross(q - p, s) / denominator;
|
||||
let u = cross(q - p, r) / denominator;
|
||||
if !(-1.0e-6..=1.0 + 1.0e-6).contains(&t)
|
||||
|| !(-1.0e-6..=1.0 + 1.0e-6).contains(&u)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let xy = p + r * t;
|
||||
let z = first.0.z + (first.1.z - first.0.z) * t;
|
||||
let sine = denominator / (r.length() * s.length()).max(1.0e-20);
|
||||
Some((DVec3::new(xy.x, xy.y, z), t, u, sine))
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["MLEDIT"] });
|
||||
@@ -12,6 +12,7 @@ pub mod geom;
|
||||
pub mod join;
|
||||
pub mod lengthen;
|
||||
pub mod mirror;
|
||||
pub mod mledit;
|
||||
pub mod offset;
|
||||
pub mod pedit;
|
||||
pub mod block_edit;
|
||||
|
||||
Reference in new issue
Block a user