forked from 1b-agent/opencadstudio-upstream
1349 lines
47 KiB
Rust
1349 lines
47 KiB
Rust
use acadrust::entities::{MLine, MLineFlags, MLineSegment};
|
|
use acadrust::types::Vector3;
|
|
use cadkernel::geom2d::{intersect::line_line, Vec2};
|
|
use cadkernel::space::Plane;
|
|
|
|
use crate::command::EntityTransform;
|
|
use crate::entities::common::{edit_prop as edit, ro_prop, square_grip};
|
|
use crate::entities::traits::{Grippable, PropertyEditable, RenderConvertible, Transformable};
|
|
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
|
use crate::scene::model::wire_model::SnapHint;
|
|
use crate::t;
|
|
|
|
/// One styled line or cap of a multiline.
|
|
pub struct MLineLine {
|
|
pub points: Vec<[f64; 3]>,
|
|
pub color: acadrust::types::Color,
|
|
pub linetype: String,
|
|
}
|
|
|
|
pub(crate) fn rebuild_mline_geometry(mline: &mut MLine) -> bool {
|
|
let count = mline.vertices.len();
|
|
mline.flags.set(MLineFlags::HAS_VERTICES, count > 0);
|
|
let Some(first) = mline.vertices.first() else {
|
|
mline.start_point = Vector3::ZERO;
|
|
return true;
|
|
};
|
|
mline.start_point = first.position;
|
|
if count == 1 {
|
|
return true;
|
|
}
|
|
|
|
let points: Vec<[f64; 3]> = mline
|
|
.vertices
|
|
.iter()
|
|
.map(|vertex| [vertex.position.x, vertex.position.y, vertex.position.z])
|
|
.collect();
|
|
let axis = points
|
|
.windows(2)
|
|
.find_map(|pair| {
|
|
(cadkernel::space::Vec3::from(pair[1]) - cadkernel::space::Vec3::from(pair[0]))
|
|
.normalize()
|
|
.map(cadkernel::space::Vec3::to_array)
|
|
})
|
|
.or_else(|| {
|
|
mline.is_closed()
|
|
.then(|| {
|
|
(cadkernel::space::Vec3::from(points[0])
|
|
- cadkernel::space::Vec3::from(points[count - 1]))
|
|
.normalize()
|
|
})
|
|
.flatten()
|
|
.map(cadkernel::space::Vec3::to_array)
|
|
});
|
|
let Some(plane) = axis.and_then(|axis| {
|
|
Plane::orthonormal(
|
|
points[0],
|
|
axis,
|
|
[mline.normal.x, mline.normal.y, mline.normal.z],
|
|
)
|
|
}) else {
|
|
return false;
|
|
};
|
|
let Some(flat): Option<Vec<Vec2>> = points
|
|
.iter()
|
|
.map(|point| plane.project(*point).map(Vec2::from))
|
|
.collect()
|
|
else {
|
|
return false;
|
|
};
|
|
let closed = mline.is_closed() && count >= 3;
|
|
let direction_after = |index: usize| {
|
|
let attempts = if closed {
|
|
count - 1
|
|
} else {
|
|
count.saturating_sub(index + 1)
|
|
};
|
|
(1..=attempts).find_map(|step| {
|
|
let next = if closed {
|
|
(index + step) % count
|
|
} else {
|
|
index + step
|
|
};
|
|
(flat[next] - flat[index]).normalize()
|
|
})
|
|
};
|
|
let direction_before = |index: usize| {
|
|
let attempts = if closed { count - 1 } else { index };
|
|
(1..=attempts).find_map(|step| {
|
|
let previous = if closed {
|
|
(index + count - step % count) % count
|
|
} else {
|
|
index - step
|
|
};
|
|
(flat[index] - flat[previous]).normalize()
|
|
})
|
|
};
|
|
|
|
for index in 0..count {
|
|
let before = direction_before(index);
|
|
let after = direction_after(index);
|
|
let Some(direction) = after.or(before) else {
|
|
continue;
|
|
};
|
|
let miter = match (before, after) {
|
|
(Some(before), Some(after)) => {
|
|
let first_offset = flat[index] + before.perpendicular();
|
|
let second_offset = flat[index] + after.perpendicular();
|
|
line_line(
|
|
first_offset.to_array(),
|
|
before.to_array(),
|
|
second_offset.to_array(),
|
|
after.to_array(),
|
|
)
|
|
.and_then(|(at, _)| {
|
|
(first_offset + before * at - flat[index]).normalize()
|
|
})
|
|
.unwrap_or_else(|| after.perpendicular())
|
|
}
|
|
(Some(direction), None) | (None, Some(direction)) => direction.perpendicular(),
|
|
(None, None) => continue,
|
|
};
|
|
let direction = plane.vector_at(direction.to_array());
|
|
let miter = plane.vector_at(miter.to_array());
|
|
mline.vertices[index].direction = Vector3::new(direction[0], direction[1], direction[2]);
|
|
mline.vertices[index].miter = Vector3::new(miter[0], miter[1], miter[2]);
|
|
}
|
|
true
|
|
}
|
|
|
|
fn adjusted_mline_endpoint(
|
|
mline: &MLine,
|
|
style: &acadrust::objects::MLineStyle,
|
|
vertex: usize,
|
|
point: [f64; 3],
|
|
) -> [f64; 3] {
|
|
let angle = if vertex == 0 {
|
|
style.start_angle
|
|
} else {
|
|
style.end_angle
|
|
};
|
|
let Some(tangent) = cadkernel::space::Vec3::new(
|
|
mline.vertices[vertex].direction.x,
|
|
mline.vertices[vertex].direction.y,
|
|
mline.vertices[vertex].direction.z,
|
|
)
|
|
.normalize()
|
|
else {
|
|
return point;
|
|
};
|
|
let Some(normal) =
|
|
cadkernel::space::Vec3::new(mline.normal.x, mline.normal.y, mline.normal.z).normalize()
|
|
else {
|
|
return point;
|
|
};
|
|
let Some(transverse) = normal.cross(tangent).normalize() else {
|
|
return point;
|
|
};
|
|
let base = cadkernel::space::Vec3::new(
|
|
mline.vertices[vertex].position.x,
|
|
mline.vertices[vertex].position.y,
|
|
mline.vertices[vertex].position.z,
|
|
);
|
|
let current = cadkernel::space::Vec3::from(point);
|
|
let tangent = if angle.tan().abs() > 1.0e-9 {
|
|
tangent * ((current - base).dot(transverse) / angle.tan())
|
|
} else {
|
|
cadkernel::space::Vec3::ZERO
|
|
};
|
|
(current + tangent).to_array()
|
|
}
|
|
|
|
pub(crate) fn mline_drawn_ranges(parameters: &[f64], length: f64) -> Vec<(f64, f64)> {
|
|
if parameters.len() <= 1 {
|
|
return vec![(0.0, length)];
|
|
}
|
|
let mut cursor = parameters[1].clamp(0.0, length);
|
|
if parameters.len() == 2 {
|
|
return (cursor < length - 1.0e-9)
|
|
.then_some((cursor, length))
|
|
.into_iter()
|
|
.collect();
|
|
}
|
|
let mut drawn = true;
|
|
let mut ranges = Vec::new();
|
|
for distance in ¶meters[2..] {
|
|
let next = (cursor + distance.max(0.0)).clamp(0.0, length);
|
|
if drawn && next - cursor > 1.0e-9 {
|
|
ranges.push((cursor, next));
|
|
}
|
|
cursor = next;
|
|
drawn = !drawn;
|
|
}
|
|
if drawn && cursor < length - 1.0e-9 {
|
|
ranges.push((cursor, length));
|
|
}
|
|
ranges
|
|
}
|
|
|
|
fn mline_fill_ranges(parameters: &[f64], length: f64) -> Vec<(f64, f64)> {
|
|
let mut cuts: 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();
|
|
cuts.sort_by(|left, right| left.0.total_cmp(&right.0));
|
|
let mut drawn = Vec::new();
|
|
let mut start = 0.0;
|
|
for cut in cuts {
|
|
if cut.0 > start + 1.0e-9 {
|
|
drawn.push((start, cut.0));
|
|
}
|
|
start = start.max(cut.1);
|
|
}
|
|
if start < length - 1.0e-9 {
|
|
drawn.push((start, length));
|
|
}
|
|
drawn
|
|
}
|
|
|
|
fn mline_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()
|
|
}
|
|
|
|
pub(crate) fn store_mline_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);
|
|
let ranges = merge_mline_ranges(
|
|
ranges
|
|
.iter()
|
|
.filter_map(|range| {
|
|
let start = range.0.clamp(0.0, length);
|
|
let end = range.1.clamp(0.0, length);
|
|
(end - start > 1.0e-9).then_some((start, end))
|
|
})
|
|
.collect(),
|
|
);
|
|
if ranges.is_empty() {
|
|
parameters.extend([0.0, 0.0]);
|
|
} else if ranges.len() == 1
|
|
&& ranges[0].0 <= 1.0e-9
|
|
&& ranges[0].1 >= length - 1.0e-9
|
|
{
|
|
parameters.push(0.0);
|
|
} else {
|
|
parameters.push(ranges[0].0);
|
|
for (index, (start, end)) in ranges.iter().enumerate() {
|
|
if index > 0 {
|
|
parameters.push(start - ranges[index - 1].1);
|
|
}
|
|
parameters.push(end - start);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn store_mline_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 remap_mline_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 shift_mline_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 merge_mline_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 mline_element_endpoints(
|
|
mline: &MLine,
|
|
vertex: usize,
|
|
element: usize,
|
|
) -> Option<(cadkernel::space::Vec3, cadkernel::space::Vec3)> {
|
|
let next = if vertex + 1 < mline.vertices.len() {
|
|
vertex + 1
|
|
} else if mline.is_closed() {
|
|
0
|
|
} else {
|
|
return None;
|
|
};
|
|
let point = |index: usize| {
|
|
let vertex = mline.vertices.get(index)?;
|
|
let offset = vertex.segments.get(element)?.parameters.first().copied()?;
|
|
let position = cadkernel::space::Vec3::new(
|
|
vertex.position.x,
|
|
vertex.position.y,
|
|
vertex.position.z,
|
|
);
|
|
let miter = cadkernel::space::Vec3::new(vertex.miter.x, vertex.miter.y, vertex.miter.z);
|
|
Some(position + miter * offset)
|
|
};
|
|
Some((point(vertex)?, point(next)?))
|
|
}
|
|
|
|
fn snapshot_mline_segment(mline: &MLine, vertex: usize) -> Option<Vec<(MLineSegment, f64)>> {
|
|
(0..mline.style_element_count)
|
|
.map(|element| {
|
|
let endpoints = mline_element_endpoints(mline, vertex, element)?;
|
|
Some((
|
|
mline.vertices.get(vertex)?.segments.get(element)?.clone(),
|
|
endpoints.0.distance(endpoints.1),
|
|
))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn restore_shifted_mline_segment(
|
|
mline: &mut MLine,
|
|
vertex: usize,
|
|
source: Vec<(MLineSegment, f64)>,
|
|
moved_start: bool,
|
|
) {
|
|
for (element, (source, source_length)) in source.into_iter().enumerate() {
|
|
let Some(endpoints) = mline_element_endpoints(mline, vertex, element) else {
|
|
continue;
|
|
};
|
|
let target_length = endpoints.0.distance(endpoints.1);
|
|
let shift = if moved_start {
|
|
target_length - source_length
|
|
} else {
|
|
0.0
|
|
};
|
|
let drawn = shift_mline_ranges(
|
|
&mline_drawn_ranges(&source.parameters, source_length),
|
|
source_length,
|
|
shift,
|
|
target_length,
|
|
);
|
|
let cuts = shift_mline_ranges(
|
|
&mline_cut_ranges(&source.area_fill_parameters, source_length),
|
|
source_length,
|
|
shift,
|
|
target_length,
|
|
);
|
|
let Some(target) = mline
|
|
.vertices
|
|
.get_mut(vertex)
|
|
.and_then(|vertex| vertex.segments.get_mut(element))
|
|
else {
|
|
continue;
|
|
};
|
|
store_mline_drawn_ranges(&mut target.parameters, target_length, &drawn);
|
|
store_mline_cut_ranges(&mut target.area_fill_parameters, target_length, &cuts);
|
|
}
|
|
}
|
|
|
|
/// Resolve a multiline into styled parallel lines in WCS.
|
|
pub fn resolved_mline_style<'a>(
|
|
m: &MLine,
|
|
document: &'a acadrust::CadDocument,
|
|
) -> Option<&'a acadrust::objects::MLineStyle> {
|
|
use acadrust::objects::ObjectType;
|
|
|
|
m.style_handle
|
|
.and_then(|handle| match document.objects.get(&handle) {
|
|
Some(ObjectType::MLineStyle(style)) => Some(style),
|
|
_ => None,
|
|
})
|
|
.or_else(|| {
|
|
document.objects.values().find_map(|object| match object {
|
|
ObjectType::MLineStyle(style)
|
|
if style.name.eq_ignore_ascii_case(&m.style_name) =>
|
|
{
|
|
Some(style)
|
|
}
|
|
_ => None,
|
|
})
|
|
})
|
|
}
|
|
|
|
pub fn mline_lines(m: &MLine, document: &acadrust::CadDocument) -> Vec<MLineLine> {
|
|
mline_lines_resolved(m, resolved_mline_style(m, document))
|
|
}
|
|
|
|
pub fn mline_lines_with_style(
|
|
m: &MLine,
|
|
style: &acadrust::objects::MLineStyle,
|
|
) -> Vec<MLineLine> {
|
|
mline_lines_resolved(m, Some(style))
|
|
}
|
|
|
|
fn mline_lines_resolved(
|
|
m: &MLine,
|
|
style: Option<&acadrust::objects::MLineStyle>,
|
|
) -> Vec<MLineLine> {
|
|
use acadrust::entities::{MLineFlags, MLineJustification};
|
|
use acadrust::types::Color;
|
|
|
|
if m.vertices.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
|
|
// (offset, colour, linetype) per element.
|
|
let elems: Vec<(f64, Color, String)> = match style {
|
|
Some(s) if !s.elements.is_empty() => s
|
|
.elements
|
|
.iter()
|
|
.map(|e| (e.offset, e.color, e.linetype.clone()))
|
|
.collect(),
|
|
_ => vec![
|
|
(0.5, Color::ByLayer, "ByLayer".to_string()),
|
|
(-0.5, Color::ByLayer, "ByLayer".to_string()),
|
|
],
|
|
};
|
|
|
|
// Stored parameters already include justification; this shift is the fallback.
|
|
let mut lo = f64::INFINITY;
|
|
let mut hi = f64::NEG_INFINITY;
|
|
for (o, _, _) in &elems {
|
|
lo = lo.min(*o);
|
|
hi = hi.max(*o);
|
|
}
|
|
let shift = match m.justification {
|
|
MLineJustification::Top => -hi,
|
|
MLineJustification::Bottom => -lo,
|
|
MLineJustification::Zero => 0.0,
|
|
};
|
|
|
|
let scale = m.scale_factor;
|
|
let closed = m.flags.contains(MLineFlags::CLOSED);
|
|
let n = m.vertices.len();
|
|
|
|
// parameter[0] stores the element distance along the miter.
|
|
let elem_off = |vi: usize, ei: usize| -> f64 {
|
|
m.vertices[vi]
|
|
.segments
|
|
.get(ei)
|
|
.and_then(|sg| sg.parameters.first().copied())
|
|
.unwrap_or_else(|| (elems[ei].0 + shift) * scale)
|
|
};
|
|
let off_pt = |vi: usize, d: f64| -> [f64; 3] {
|
|
let v = &m.vertices[vi];
|
|
(cadkernel::space::Vec3::new(v.position.x, v.position.y, v.position.z)
|
|
+ cadkernel::space::Vec3::new(v.miter.x, v.miter.y, v.miter.z) * d)
|
|
.to_array()
|
|
};
|
|
let endpoint_pt = |vi: usize, ei: usize| -> [f64; 3] {
|
|
let point = off_pt(vi, elem_off(vi, ei));
|
|
if closed || (vi != 0 && vi + 1 != n) {
|
|
return point;
|
|
}
|
|
let Some(style) = style else {
|
|
return point;
|
|
};
|
|
adjusted_mline_endpoint(m, style, vi, point)
|
|
};
|
|
|
|
let mut out: Vec<MLineLine> = Vec::with_capacity(elems.len() + 2);
|
|
for (ei, (_, color, linetype)) in elems.iter().enumerate() {
|
|
let mut pts: Vec<[f64; 3]> = Vec::new();
|
|
// Parameters store the start offset, then alternating drawn and gap lengths.
|
|
let seg_count = if closed { n } else { n.saturating_sub(1) };
|
|
let mut pen_at_end = false;
|
|
for k in 0..seg_count {
|
|
let vi = k;
|
|
let wi = (k + 1) % n;
|
|
let a = if !closed && vi == 0 {
|
|
endpoint_pt(vi, ei)
|
|
} else {
|
|
off_pt(vi, elem_off(vi, ei))
|
|
};
|
|
let b = if !closed && wi + 1 == n {
|
|
endpoint_pt(wi, ei)
|
|
} else {
|
|
off_pt(wi, elem_off(wi, ei))
|
|
};
|
|
let a = cadkernel::space::Vec3::from(a);
|
|
let b = cadkernel::space::Vec3::from(b);
|
|
let segment = b - a;
|
|
let len = segment.length();
|
|
let Some(direction) = segment.normalize() else {
|
|
continue;
|
|
};
|
|
let at = |t: f64| (a + direction * t).to_array();
|
|
let parameters: &[f64] = m
|
|
.vertices[vi]
|
|
.segments
|
|
.get(ei)
|
|
.map(|segment| segment.parameters.as_slice())
|
|
.unwrap_or(&[]);
|
|
let runs = mline_drawn_ranges(parameters, len);
|
|
for (ri, (t0, t1)) in runs.iter().enumerate() {
|
|
let continuous = pen_at_end && ri == 0 && *t0 <= 1e-9 && !pts.is_empty();
|
|
if !continuous {
|
|
if !pts.is_empty() {
|
|
pts.push([f64::NAN; 3]);
|
|
}
|
|
pts.push(at(*t0));
|
|
}
|
|
pts.push(at(*t1));
|
|
}
|
|
pen_at_end = runs.last().is_some_and(|(_, t1)| (len - t1).abs() <= 1e-9);
|
|
}
|
|
if pts.len() < 2 {
|
|
continue;
|
|
}
|
|
out.push(MLineLine {
|
|
points: pts,
|
|
color: *color,
|
|
linetype: linetype.clone(),
|
|
});
|
|
}
|
|
|
|
// Style-defined joints and end caps.
|
|
if let Some(s) = style {
|
|
let outer_points = |vi: usize, endpoint: bool| -> Option<([f64; 3], [f64; 3])> {
|
|
let mut order: Vec<usize> = (0..elems.len()).collect();
|
|
order.sort_by(|a, b| elem_off(vi, *a).total_cmp(&elem_off(vi, *b)));
|
|
let first = *order.first()?;
|
|
let last = *order.last()?;
|
|
let point = |ei| {
|
|
if endpoint {
|
|
endpoint_pt(vi, ei)
|
|
} else {
|
|
off_pt(vi, elem_off(vi, ei))
|
|
}
|
|
};
|
|
Some((point(first), point(last)))
|
|
};
|
|
|
|
if s.flags.display_joints {
|
|
let vertices: Box<dyn Iterator<Item = usize>> = if closed {
|
|
Box::new(0..n)
|
|
} else {
|
|
Box::new(1..n.saturating_sub(1))
|
|
};
|
|
for vi in vertices {
|
|
if let Some((a, b)) = outer_points(vi, false) {
|
|
out.push(MLineLine {
|
|
points: vec![a, b],
|
|
color: Color::ByLayer,
|
|
linetype: "ByLayer".to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if !closed && n >= 2 {
|
|
let start_suppressed = m.flags.contains(MLineFlags::NO_START_CAPS);
|
|
let end_suppressed = m.flags.contains(MLineFlags::NO_END_CAPS);
|
|
for (vi, start, suppressed, square, inner, round) in [
|
|
(
|
|
0,
|
|
true,
|
|
start_suppressed,
|
|
s.flags.start_square_cap,
|
|
s.flags.start_inner_arcs_cap,
|
|
s.flags.start_round_cap,
|
|
),
|
|
(
|
|
n - 1,
|
|
false,
|
|
end_suppressed,
|
|
s.flags.end_square_cap,
|
|
s.flags.end_inner_arcs_cap,
|
|
s.flags.end_round_cap,
|
|
),
|
|
] {
|
|
if suppressed {
|
|
continue;
|
|
}
|
|
let Some((a, b)) = outer_points(vi, true) else {
|
|
continue;
|
|
};
|
|
if square {
|
|
out.push(MLineLine {
|
|
points: vec![a, b],
|
|
color: Color::ByLayer,
|
|
linetype: "ByLayer".to_string(),
|
|
});
|
|
}
|
|
let direction = [
|
|
m.vertices[vi].direction.x,
|
|
m.vertices[vi].direction.y,
|
|
m.vertices[vi].direction.z,
|
|
];
|
|
if round {
|
|
out.push(MLineLine {
|
|
points: semicircle_cap(a, b, direction, start),
|
|
color: Color::ByLayer,
|
|
linetype: "ByLayer".to_string(),
|
|
});
|
|
}
|
|
if inner && elems.len() > 2 {
|
|
let mut order: Vec<usize> = (0..elems.len()).collect();
|
|
order.sort_by(|left, right| {
|
|
elem_off(vi, *left).total_cmp(&elem_off(vi, *right))
|
|
});
|
|
for pair in order.windows(2) {
|
|
out.push(MLineLine {
|
|
points: semicircle_cap(
|
|
endpoint_pt(vi, pair[0]),
|
|
endpoint_pt(vi, pair[1]),
|
|
direction,
|
|
start,
|
|
),
|
|
color: Color::ByLayer,
|
|
linetype: "ByLayer".to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out
|
|
}
|
|
|
|
fn semicircle_cap(
|
|
first: [f64; 3],
|
|
second: [f64; 3],
|
|
direction: [f64; 3],
|
|
start: bool,
|
|
) -> Vec<[f64; 3]> {
|
|
let first = cadkernel::space::Vec3::from(first);
|
|
let second = cadkernel::space::Vec3::from(second);
|
|
let center = (first + second) * 0.5;
|
|
let transverse = (first - second) * 0.5;
|
|
let radius = transverse.length();
|
|
let Some(x_axis) = transverse.normalize() else {
|
|
return Vec::new();
|
|
};
|
|
let Some(direction) = cadkernel::space::Vec3::from(direction).normalize() else {
|
|
return Vec::new();
|
|
};
|
|
let y_axis = if start { -direction } else { direction };
|
|
cadkernel::space::PlanarCurve::new(
|
|
Plane::from_axes(center.to_array(), x_axis.to_array(), y_axis.to_array()),
|
|
cadkernel::geom2d::Curve::Arc(cadkernel::geom2d::Arc {
|
|
centre: [0.0, 0.0],
|
|
radius,
|
|
start_angle: 0.0,
|
|
end_angle: std::f64::consts::PI,
|
|
}),
|
|
)
|
|
.tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE)
|
|
}
|
|
|
|
pub fn mline_fill_triangles_with_style(
|
|
m: &MLine,
|
|
style: &acadrust::objects::MLineStyle,
|
|
) -> Vec<[f64; 3]> {
|
|
if !style.flags.fill_on || m.vertices.len() < 2 || style.elements.len() < 2 {
|
|
return Vec::new();
|
|
}
|
|
let (low_index, high_index) = style
|
|
.elements
|
|
.iter()
|
|
.enumerate()
|
|
.fold((0, 0), |(low, high), (index, element)| {
|
|
let offset = element.offset * m.scale_factor;
|
|
let low = if offset < style.elements[low].offset * m.scale_factor {
|
|
index
|
|
} else {
|
|
low
|
|
};
|
|
let high = if offset > style.elements[high].offset * m.scale_factor {
|
|
index
|
|
} else {
|
|
high
|
|
};
|
|
(low, high)
|
|
});
|
|
let minimum = style
|
|
.elements
|
|
.iter()
|
|
.map(|element| element.offset)
|
|
.fold(f64::INFINITY, f64::min);
|
|
let maximum = style
|
|
.elements
|
|
.iter()
|
|
.map(|element| element.offset)
|
|
.fold(f64::NEG_INFINITY, f64::max);
|
|
let shift = match m.justification {
|
|
acadrust::entities::MLineJustification::Top => -maximum,
|
|
acadrust::entities::MLineJustification::Zero => 0.0,
|
|
acadrust::entities::MLineJustification::Bottom => -minimum,
|
|
};
|
|
let offset_point = |vertex: usize, element: usize, endpoint: bool| -> [f64; 3] {
|
|
let item = &m.vertices[vertex];
|
|
let distance = item
|
|
.segments
|
|
.get(element)
|
|
.and_then(|segment| segment.parameters.first())
|
|
.copied()
|
|
.unwrap_or((style.elements[element].offset + shift) * m.scale_factor);
|
|
let point = (cadkernel::space::Vec3::new(
|
|
item.position.x,
|
|
item.position.y,
|
|
item.position.z,
|
|
) + cadkernel::space::Vec3::new(item.miter.x, item.miter.y, item.miter.z) * distance)
|
|
.to_array();
|
|
if endpoint {
|
|
adjusted_mline_endpoint(m, style, vertex, point)
|
|
} else {
|
|
point
|
|
}
|
|
};
|
|
let closed = m.flags.contains(MLineFlags::CLOSED);
|
|
let segment_count = if closed {
|
|
m.vertices.len()
|
|
} else {
|
|
m.vertices.len() - 1
|
|
};
|
|
let mut triangles = Vec::with_capacity(segment_count * 6);
|
|
for vertex in 0..segment_count {
|
|
let next = (vertex + 1) % m.vertices.len();
|
|
let start_endpoint = !closed && vertex == 0;
|
|
let end_endpoint = !closed && next + 1 == m.vertices.len();
|
|
let low_start = cadkernel::space::Vec3::from(offset_point(
|
|
vertex,
|
|
low_index,
|
|
start_endpoint,
|
|
));
|
|
let low_end = cadkernel::space::Vec3::from(offset_point(next, low_index, end_endpoint));
|
|
let high_start = cadkernel::space::Vec3::from(offset_point(
|
|
vertex,
|
|
high_index,
|
|
start_endpoint,
|
|
));
|
|
let high_end = cadkernel::space::Vec3::from(offset_point(next, high_index, end_endpoint));
|
|
let low_length = low_start.distance(low_end);
|
|
let high_length = high_start.distance(high_end);
|
|
if low_length <= 1.0e-12 || high_length <= 1.0e-12 {
|
|
continue;
|
|
}
|
|
let fill_ranges = |element: usize, length: f64| {
|
|
let parameters = m.vertices[vertex]
|
|
.segments
|
|
.get(element)
|
|
.map(|segment| segment.area_fill_parameters.as_slice())
|
|
.unwrap_or(&[]);
|
|
mline_fill_ranges(parameters, length)
|
|
.into_iter()
|
|
.map(|(start, end)| (start / length, end / length))
|
|
.collect::<Vec<_>>()
|
|
};
|
|
let low_ranges = fill_ranges(low_index, low_length);
|
|
let high_ranges = fill_ranges(high_index, high_length);
|
|
for low in &low_ranges {
|
|
for high in &high_ranges {
|
|
let start = low.0.max(high.0);
|
|
let end = low.1.min(high.1);
|
|
if end - start <= 1.0e-9 {
|
|
continue;
|
|
}
|
|
let a = low_start.lerp(low_end, start).to_array();
|
|
let b = high_start.lerp(high_end, start).to_array();
|
|
let c = high_start.lerp(high_end, end).to_array();
|
|
let d = low_start.lerp(low_end, end).to_array();
|
|
triangles.extend([a, b, c, a, c, d]);
|
|
}
|
|
}
|
|
}
|
|
triangles
|
|
}
|
|
|
|
impl RenderConvertible for MLine {
|
|
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
|
if self.vertices.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
// Picking uses one NaN-separated line list; rendering keeps style colours.
|
|
let lines = mline_lines(self, document);
|
|
let mut pts: Vec<[f64; 3]> = Vec::new();
|
|
for (i, l) in lines.iter().enumerate() {
|
|
if i > 0 {
|
|
pts.push([f64::NAN; 3]);
|
|
}
|
|
pts.extend_from_slice(&l.points);
|
|
}
|
|
|
|
let key_verts: Vec<[f64; 3]> = self
|
|
.vertices
|
|
.iter()
|
|
.map(|v| [v.position.x, v.position.y, v.position.z])
|
|
.collect();
|
|
|
|
let snap_pts = self
|
|
.vertices
|
|
.iter()
|
|
.map(|v| {
|
|
(
|
|
glam::DVec3::new(v.position.x, v.position.y, v.position.z),
|
|
SnapHint::Node,
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
Some(RenderEntity {
|
|
pick_tris: Vec::new(),
|
|
object: RenderObject::Lines(pts),
|
|
snap_pts,
|
|
tangent_geoms: vec![],
|
|
key_vertices: key_verts,
|
|
fill_tris: vec![],
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Grippable for MLine {
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
self.vertices
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, v)| {
|
|
square_grip(
|
|
i,
|
|
glam::DVec3::new(v.position.x, v.position.y, v.position.z),
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
let Some(vertex) = self.vertices.get(grip_id) else {
|
|
return;
|
|
};
|
|
let position = match apply {
|
|
GripApply::Translate(delta) => acadrust::types::Vector3::new(
|
|
vertex.position.x + delta.x as f64,
|
|
vertex.position.y + delta.y as f64,
|
|
vertex.position.z + delta.z as f64,
|
|
),
|
|
GripApply::Absolute(point) => {
|
|
acadrust::types::Vector3::new(point.x as f64, point.y as f64, point.z as f64)
|
|
}
|
|
};
|
|
let count = self.vertices.len();
|
|
let segment_count = if self.is_closed() {
|
|
count
|
|
} else {
|
|
count.saturating_sub(1)
|
|
};
|
|
let mut affected = Vec::new();
|
|
let candidates = if self.is_closed() {
|
|
vec![
|
|
((grip_id + count - 2) % count, false),
|
|
((grip_id + count - 1) % count, false),
|
|
(grip_id, true),
|
|
((grip_id + 1) % count, true),
|
|
]
|
|
} else {
|
|
let mut candidates = Vec::new();
|
|
if grip_id >= 2 {
|
|
candidates.push((grip_id - 2, false));
|
|
}
|
|
if grip_id >= 1 {
|
|
candidates.push((grip_id - 1, false));
|
|
}
|
|
if grip_id < segment_count {
|
|
candidates.push((grip_id, true));
|
|
}
|
|
if grip_id + 1 < segment_count {
|
|
candidates.push((grip_id + 1, true));
|
|
}
|
|
candidates
|
|
};
|
|
for (segment, moved_start) in candidates {
|
|
if segment >= segment_count
|
|
|| affected
|
|
.iter()
|
|
.any(|(existing, _, _)| *existing == segment)
|
|
{
|
|
continue;
|
|
}
|
|
if let Some(source) = snapshot_mline_segment(self, segment) {
|
|
affected.push((segment, moved_start, source));
|
|
}
|
|
}
|
|
let offsets = mline_perpendicular_offsets(self);
|
|
self.vertices[grip_id].position = position;
|
|
if rebuild_mline_geometry(self) {
|
|
restore_mline_offsets(self, &offsets);
|
|
for (segment, moved_start, source) in affected {
|
|
restore_shifted_mline_segment(self, segment, source, moved_start);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
|
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
|
vec![
|
|
GripMenuItem {
|
|
label: "Stretch",
|
|
action: GripMenuAction::Stretch,
|
|
},
|
|
GripMenuItem {
|
|
label: "Add Vertex",
|
|
action: GripMenuAction::AddVertex,
|
|
},
|
|
GripMenuItem {
|
|
label: "Remove Vertex",
|
|
action: GripMenuAction::RemoveVertex,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
|
use crate::scene::model::object::GripMenuAction as A;
|
|
let n = self.vertices.len();
|
|
match action {
|
|
A::AddVertex if grip_id < n => {
|
|
let insert = if grip_id + 1 < n {
|
|
grip_id + 1
|
|
} else if self.is_closed() {
|
|
n
|
|
} else {
|
|
return;
|
|
};
|
|
let next = (grip_id + 1) % n;
|
|
let v0 = &self.vertices[grip_id];
|
|
let v1 = &self.vertices[next];
|
|
let mut new_v = v0.clone();
|
|
new_v.position.x = (v0.position.x + v1.position.x) * 0.5;
|
|
new_v.position.y = (v0.position.y + v1.position.y) * 0.5;
|
|
new_v.position.z = (v0.position.z + v1.position.z) * 0.5;
|
|
let point = cadkernel::space::Vec3::new(
|
|
new_v.position.x,
|
|
new_v.position.y,
|
|
new_v.position.z,
|
|
);
|
|
let source: Option<Vec<_>> = (0..self.style_element_count)
|
|
.map(|element| {
|
|
let endpoints = mline_element_endpoints(self, grip_id, element)?;
|
|
let direction = endpoints.1 - endpoints.0;
|
|
let length = direction.length();
|
|
let split = (point - endpoints.0)
|
|
.dot(direction.normalize()?)
|
|
.clamp(0.0, length);
|
|
Some((
|
|
self.vertices[grip_id].segments.get(element)?.clone(),
|
|
length,
|
|
split,
|
|
))
|
|
})
|
|
.collect();
|
|
let Some(source) = source else {
|
|
return;
|
|
};
|
|
self.vertices.insert(insert, new_v);
|
|
let offsets = mline_perpendicular_offsets(self);
|
|
if !rebuild_mline_geometry(self) {
|
|
return;
|
|
}
|
|
restore_mline_offsets(self, &offsets);
|
|
for (element, (source, source_length, split)) in source.into_iter().enumerate() {
|
|
let Some(first) = mline_element_endpoints(self, grip_id, element) else {
|
|
continue;
|
|
};
|
|
let Some(second) = mline_element_endpoints(self, insert, element) else {
|
|
continue;
|
|
};
|
|
let first_length = first.0.distance(first.1);
|
|
let second_length = second.0.distance(second.1);
|
|
let drawn = mline_drawn_ranges(&source.parameters, source_length);
|
|
let cuts = mline_cut_ranges(&source.area_fill_parameters, source_length);
|
|
let first_drawn = remap_mline_ranges(&drawn, 0.0, split, first_length);
|
|
let second_drawn =
|
|
remap_mline_ranges(&drawn, split, source_length, second_length);
|
|
let first_cuts = remap_mline_ranges(&cuts, 0.0, split, first_length);
|
|
let second_cuts =
|
|
remap_mline_ranges(&cuts, split, source_length, second_length);
|
|
let first = &mut self.vertices[grip_id].segments[element];
|
|
store_mline_drawn_ranges(
|
|
&mut first.parameters,
|
|
first_length,
|
|
&first_drawn,
|
|
);
|
|
store_mline_cut_ranges(
|
|
&mut first.area_fill_parameters,
|
|
first_length,
|
|
&first_cuts,
|
|
);
|
|
let second = &mut self.vertices[insert].segments[element];
|
|
store_mline_drawn_ranges(
|
|
&mut second.parameters,
|
|
second_length,
|
|
&second_drawn,
|
|
);
|
|
store_mline_cut_ranges(
|
|
&mut second.area_fill_parameters,
|
|
second_length,
|
|
&second_cuts,
|
|
);
|
|
}
|
|
}
|
|
A::RemoveVertex
|
|
if grip_id < n && n > 2 && (!self.is_closed() || n > 3) =>
|
|
{
|
|
let merge = self.is_closed() || (grip_id > 0 && grip_id + 1 < n);
|
|
let previous = if grip_id == 0 { n - 1 } else { grip_id - 1 };
|
|
let source = merge.then(|| {
|
|
let first = snapshot_mline_segment(self, previous)?;
|
|
let second = snapshot_mline_segment(self, grip_id)?;
|
|
Some((first, second))
|
|
});
|
|
let source = match source {
|
|
Some(Some(source)) => Some(source),
|
|
Some(None) => return,
|
|
None => None,
|
|
};
|
|
let mut peripheral = Vec::new();
|
|
let candidates = if self.is_closed() {
|
|
vec![
|
|
((previous + n - 1) % n, false),
|
|
((grip_id + 1) % n, true),
|
|
]
|
|
} else {
|
|
let mut candidates = Vec::new();
|
|
if grip_id >= 2 {
|
|
candidates.push((grip_id - 2, false));
|
|
}
|
|
if grip_id + 2 < n {
|
|
candidates.push((grip_id + 1, true));
|
|
}
|
|
candidates
|
|
};
|
|
for (segment, moved_start) in candidates {
|
|
if (merge && (segment == previous || segment == grip_id))
|
|
|| peripheral
|
|
.iter()
|
|
.any(|(existing, _, _)| *existing == segment)
|
|
{
|
|
continue;
|
|
}
|
|
if let Some(snapshot) = snapshot_mline_segment(self, segment) {
|
|
let target = if segment > grip_id {
|
|
segment - 1
|
|
} else {
|
|
segment
|
|
};
|
|
peripheral.push((target, moved_start, snapshot));
|
|
}
|
|
}
|
|
let mut offsets = mline_perpendicular_offsets(self);
|
|
self.vertices.remove(grip_id);
|
|
offsets.remove(grip_id);
|
|
if !rebuild_mline_geometry(self) {
|
|
return;
|
|
}
|
|
restore_mline_offsets(self, &offsets);
|
|
for (segment, moved_start, snapshot) in peripheral {
|
|
restore_shifted_mline_segment(self, segment, snapshot, moved_start);
|
|
}
|
|
if let Some((first, second)) = source {
|
|
let previous = if grip_id == 0 {
|
|
self.vertices.len() - 1
|
|
} else {
|
|
grip_id - 1
|
|
};
|
|
for (element, ((first, first_length), (second, second_length))) in
|
|
first.into_iter().zip(second).enumerate()
|
|
{
|
|
let Some(endpoints) = mline_element_endpoints(self, previous, element)
|
|
else {
|
|
continue;
|
|
};
|
|
let target_length = endpoints.0.distance(endpoints.1);
|
|
let total = first_length + second_length;
|
|
let mut drawn = mline_drawn_ranges(&first.parameters, first_length);
|
|
drawn.extend(
|
|
mline_drawn_ranges(&second.parameters, second_length)
|
|
.into_iter()
|
|
.map(|range| {
|
|
(range.0 + first_length, range.1 + first_length)
|
|
}),
|
|
);
|
|
let mut cuts =
|
|
mline_cut_ranges(&first.area_fill_parameters, first_length);
|
|
cuts.extend(
|
|
mline_cut_ranges(&second.area_fill_parameters, second_length)
|
|
.into_iter()
|
|
.map(|range| {
|
|
(range.0 + first_length, range.1 + first_length)
|
|
}),
|
|
);
|
|
let drawn = merge_mline_ranges(remap_mline_ranges(
|
|
&drawn,
|
|
0.0,
|
|
total,
|
|
target_length,
|
|
));
|
|
let cuts = merge_mline_ranges(remap_mline_ranges(
|
|
&cuts,
|
|
0.0,
|
|
total,
|
|
target_length,
|
|
));
|
|
let target = &mut self.vertices[previous].segments[element];
|
|
store_mline_drawn_ranges(
|
|
&mut target.parameters,
|
|
target_length,
|
|
&drawn,
|
|
);
|
|
store_mline_cut_ranges(
|
|
&mut target.area_fill_parameters,
|
|
target_length,
|
|
&cuts,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PropertyEditable for MLine {
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
|
let just_str = match self.justification {
|
|
acadrust::entities::MLineJustification::Top => "Top",
|
|
acadrust::entities::MLineJustification::Zero => "Zero",
|
|
acadrust::entities::MLineJustification::Bottom => "Bottom",
|
|
};
|
|
vec![PropSection {
|
|
title: t!("Misc").into_owned(),
|
|
props: vec![
|
|
ro_prop(t!("Style").as_ref(), "ml_style", self.style_name.clone()),
|
|
Property {
|
|
label: t!("Style justification").into_owned(),
|
|
field: "ml_justification",
|
|
value: PropValue::Choice {
|
|
selected: just_str.to_string(),
|
|
options: ["Top", "Zero", "Bottom"]
|
|
.into_iter()
|
|
.map(str::to_string)
|
|
.collect(),
|
|
},
|
|
},
|
|
edit(t!("Scale").as_ref(), "ml_scale", self.scale_factor),
|
|
],
|
|
}]
|
|
}
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
|
match field {
|
|
"ml_closed" => {
|
|
let closed = if value == "toggle" {
|
|
!self.flags.contains(acadrust::entities::MLineFlags::CLOSED)
|
|
} else {
|
|
value == "true"
|
|
};
|
|
let offsets = mline_perpendicular_offsets(self);
|
|
self.flags.set(
|
|
acadrust::entities::MLineFlags::CLOSED,
|
|
closed && self.vertices.len() >= 3,
|
|
);
|
|
rebuild_mline_geometry(self);
|
|
restore_mline_offsets(self, &offsets);
|
|
return;
|
|
}
|
|
"ml_justification" => {
|
|
self.justification = match value {
|
|
"Top" => acadrust::entities::MLineJustification::Top,
|
|
"Bottom" => acadrust::entities::MLineJustification::Bottom,
|
|
_ => acadrust::entities::MLineJustification::Zero,
|
|
};
|
|
return;
|
|
}
|
|
_ => {}
|
|
}
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
return;
|
|
};
|
|
if field == "ml_scale" {
|
|
set_mline_scale(self, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_mline_scale(mline: &mut MLine, scale: f64) {
|
|
let old = mline.scale_factor;
|
|
if !scale.is_finite() || scale == 0.0 || !old.is_finite() {
|
|
return;
|
|
}
|
|
if old == 0.0 {
|
|
mline.scale_factor = scale;
|
|
return;
|
|
}
|
|
let ratio = scale / old;
|
|
if !ratio.is_finite() {
|
|
return;
|
|
}
|
|
if mline
|
|
.vertices
|
|
.iter()
|
|
.flat_map(|vertex| &vertex.segments)
|
|
.filter_map(|segment| segment.parameters.first())
|
|
.any(|offset| !offset.is_finite() || !(offset * ratio).is_finite())
|
|
{
|
|
return;
|
|
}
|
|
for segment in mline
|
|
.vertices
|
|
.iter_mut()
|
|
.flat_map(|vertex| vertex.segments.iter_mut())
|
|
{
|
|
if let Some(offset) = segment.parameters.first_mut() {
|
|
*offset *= ratio;
|
|
}
|
|
}
|
|
mline.scale_factor = scale;
|
|
}
|
|
|
|
fn mline_vertex_factor(mline: &MLine, index: usize) -> f64 {
|
|
let vertex = &mline.vertices[index];
|
|
let normal = cadkernel::space::Vec3::new(mline.normal.x, mline.normal.y, mline.normal.z)
|
|
.normalize()
|
|
.unwrap_or(cadkernel::space::Vec3::Z);
|
|
let direction =
|
|
cadkernel::space::Vec3::new(vertex.direction.x, vertex.direction.y, vertex.direction.z)
|
|
.normalize()
|
|
.unwrap_or(cadkernel::space::Vec3::X);
|
|
let miter = cadkernel::space::Vec3::new(vertex.miter.x, vertex.miter.y, vertex.miter.z)
|
|
.normalize()
|
|
.unwrap_or(cadkernel::space::Vec3::Y);
|
|
miter.dot(normal.cross(direction)).abs().max(1.0e-9)
|
|
}
|
|
|
|
fn mline_perpendicular_offsets(mline: &MLine) -> Vec<Vec<Option<f64>>> {
|
|
mline
|
|
.vertices
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, vertex)| {
|
|
let factor = mline_vertex_factor(mline, index);
|
|
vertex
|
|
.segments
|
|
.iter()
|
|
.map(|segment| segment.parameters.first().map(|value| value * factor))
|
|
.collect()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn restore_mline_offsets(mline: &mut MLine, offsets: &[Vec<Option<f64>>]) {
|
|
for index in 0..mline.vertices.len().min(offsets.len()) {
|
|
let factor = mline_vertex_factor(mline, index);
|
|
for (segment, offset) in mline.vertices[index]
|
|
.segments
|
|
.iter_mut()
|
|
.zip(&offsets[index])
|
|
{
|
|
if let Some(offset) = offset {
|
|
if let Some(first) = segment.parameters.first_mut() {
|
|
*first = *offset / factor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Transformable for MLine {
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
|
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
|
for v in &mut entity.vertices {
|
|
crate::scene::view::transform::reflect_xy_point(
|
|
&mut v.position.x,
|
|
&mut v.position.y,
|
|
p1,
|
|
p2,
|
|
);
|
|
}
|
|
crate::scene::view::transform::reflect_xy_point(
|
|
&mut entity.start_point.x,
|
|
&mut entity.start_point.y,
|
|
p1,
|
|
p2,
|
|
);
|
|
});
|
|
}
|
|
}
|