forked from 1b-agent/opencadstudio-upstream
Merge PR #1179: Add tolerant OVERKILL cleanup options
# Conflicts: # src/app/commands/styleprops.rs
This commit is contained in:
commit
32952e3eda
5 files changed
+299
-61
No files matched your search
Generated
+2
-2
@@ -894,7 +894,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b95889d5e2d5271fdf4d43fb1d883f5b091dd738#b95889d5e2d5271fdf4d43fb1d883f5b091dd738"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b414090f1b54191242d2f880cfb1fa08050698bd#b414090f1b54191242d2f880cfb1fa08050698bd"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
@@ -905,7 +905,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cadkernel-constraints"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b95889d5e2d5271fdf4d43fb1d883f5b091dd738#b95889d5e2d5271fdf4d43fb1d883f5b091dd738"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b414090f1b54191242d2f880cfb1fa08050698bd#b414090f1b54191242d2f880cfb1fa08050698bd"
|
||||
dependencies = [
|
||||
"nalgebra",
|
||||
]
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "b49cbbfc49ba267712045831a601d2f3d0ed386a", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b95889d5e2d5271fdf4d43fb1d883f5b091dd738", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b95889d5e2d5271fdf4d43fb1d883f5b091dd738" }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b414090f1b54191242d2f880cfb1fa08050698bd", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b414090f1b54191242d2f880cfb1fa08050698bd" }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
+116
-57
@@ -739,68 +739,127 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// ── OVERKILL — delete duplicate (identical) objects ──────────
|
||||
// Removes objects that are identical in geometry AND properties to
|
||||
// another object (compared with the handle ignored). Operates on
|
||||
// the current selection, gathering objects first when nothing is
|
||||
// selected. Conservative: only exact duplicates are removed.
|
||||
"OVERKILL" => {
|
||||
use acadrust::Handle;
|
||||
if self.tabs[i].scene.selected.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let selection = SelectObjectsCommand::new("OVERKILL");
|
||||
self.command_line.push_info(&selection.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(selection));
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
let selected: std::collections::HashSet<u64> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h.value())
|
||||
.collect();
|
||||
// Capture (handle, type-name, handle-normalized clone) for each
|
||||
// candidate while the document is borrowed immutably.
|
||||
let candidates: Vec<(Handle, String, acadrust::EntityType)> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| {
|
||||
selected.contains(&e.common().handle.value())
|
||||
&& !self.tabs[i].scene.is_layer_locked(e.common().handle)
|
||||
})
|
||||
.map(|e| {
|
||||
let key = crate::entities::names::dxf_name(e).to_string();
|
||||
let mut norm = e.clone();
|
||||
norm.common_mut().handle = Handle::NULL;
|
||||
(e.common().handle, key, norm)
|
||||
})
|
||||
.collect();
|
||||
// Bucket by (type, layer) so only like objects are compared.
|
||||
let mut kept: Vec<(String, acadrust::EntityType)> = Vec::new();
|
||||
let mut dups: Vec<Handle> = Vec::new();
|
||||
for (h, key, norm) in &candidates {
|
||||
let bucket = format!("{key}\u{0}{}", norm.common().layer);
|
||||
if kept.iter().any(|(b, e)| b == &bucket && e == norm) {
|
||||
dups.push(*h);
|
||||
} else {
|
||||
kept.push((bucket, norm.clone()));
|
||||
// OVERKILL gathers objects, exposes cleanup settings, then applies one undo group.
|
||||
"OVERKILL" | "-OVERKILL" => {
|
||||
let handles=self.tabs[i].scene.selected_entities().iter().map(|(h,_)|*h).collect();
|
||||
let command=crate::modules::draw::modify::overkill::OverkillCommand::new(handles);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd=Some(Box::new(command));
|
||||
}
|
||||
value if value.starts_with("OVERKILL_APPLY ") => {
|
||||
use crate::modules::draw::modify::overkill::{normalized,optimize};
|
||||
let values:Vec<_>=value.split_whitespace().skip(1).collect();
|
||||
if values.len()!=6 { return Some(self.finish_dispatch(cmd)); }
|
||||
let tolerance=values[0].parse::<f64>().unwrap_or(1e-6);
|
||||
let ignore=values[1].parse::<u16>().unwrap_or(0);
|
||||
let optimize_plines=values[2]=="1";
|
||||
let overlap=values[3]=="1";
|
||||
let end_to_end=values[4]=="1";
|
||||
let preserve_associative=values[5]=="1";
|
||||
let mut candidates:Vec<_>=self.tabs[i].scene.selected_entities().into_iter()
|
||||
.filter(|(h,e)|!self.tabs[i].scene.is_layer_locked(*h)
|
||||
&& (!preserve_associative||e.common().reactors.is_empty()))
|
||||
.map(|(h,e)|(h,e.clone())).collect();
|
||||
candidates.sort_by_key(|(h,_)|h.value());
|
||||
let mut changed=std::collections::HashSet::new();
|
||||
if optimize_plines { for (h,e) in &mut candidates {
|
||||
let before=e.clone(); optimize(e,tolerance); if *e!=before {changed.insert(*h);}
|
||||
} }
|
||||
let mut removed=std::collections::HashSet::new();
|
||||
// Iterate to a fixed point: a bridge can join two previously disjoint intervals.
|
||||
loop {
|
||||
let mut progress=false;
|
||||
for a in 0..candidates.len() {
|
||||
if removed.contains(&candidates[a].0) {continue;}
|
||||
for b in a+1..candidates.len() {
|
||||
if removed.contains(&candidates[b].0) {continue;}
|
||||
let left=normalized(&candidates[a].1,ignore);
|
||||
let right=normalized(&candidates[b].1,ignore);
|
||||
if left==right {
|
||||
removed.insert(candidates[b].0);progress=true;continue;
|
||||
}
|
||||
if overlap {
|
||||
let contained = |circle: &acadrust::entities::Circle, arc: &acadrust::entities::Arc| {
|
||||
circle.common == arc.common && circle.thickness == arc.thickness
|
||||
&& cadkernel::space::arc_union::circle_contains_arc(
|
||||
[circle.center.x,circle.center.y,circle.center.z],
|
||||
[circle.normal.x,circle.normal.y,circle.normal.z],circle.radius,
|
||||
cadkernel::space::arc_union::CircularArc { center:[arc.center.x,arc.center.y,arc.center.z],
|
||||
normal:[arc.normal.x,arc.normal.y,arc.normal.z],radius:arc.radius,start:arc.start_angle,end:arc.end_angle })
|
||||
};
|
||||
match (&left,&right) {
|
||||
(acadrust::EntityType::Circle(circle),acadrust::EntityType::Arc(arc)) if contained(circle,arc) => {
|
||||
removed.insert(candidates[b].0);progress=true;continue;
|
||||
}
|
||||
(acadrust::EntityType::Arc(arc),acadrust::EntityType::Circle(circle)) if contained(circle,arc) => {
|
||||
removed.insert(candidates[a].0);progress=true;break;
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
if let (acadrust::EntityType::Arc(l),acadrust::EntityType::Arc(r))=(&left,&right) {
|
||||
use cadkernel::space::arc_union::{CircularArc,ArcUnionKind,circular_arc_union};
|
||||
if l.common!=r.common || l.thickness!=r.thickness {continue;}
|
||||
let arc=|v:&acadrust::entities::Arc|CircularArc{center:[v.center.x,v.center.y,v.center.z],normal:[v.normal.x,v.normal.y,v.normal.z],radius:v.radius,start:v.start_angle,end:v.end_angle};
|
||||
let Some(union)=circular_arc_union(arc(l),arc(r),tolerance) else {continue;};
|
||||
let allowed=match union.kind {ArcUnionKind::Duplicate=>true,ArcUnionKind::Overlap=>overlap,ArcUnionKind::EndToEnd=>end_to_end};
|
||||
if !allowed {continue;}
|
||||
if let acadrust::EntityType::Arc(source)=&candidates[a].1 {
|
||||
let replacement=if union.full_circle {
|
||||
let mut circle=acadrust::entities::Circle::new();
|
||||
circle.common=source.common.clone();circle.center=source.center.clone();circle.normal=source.normal.clone();circle.radius=source.radius;circle.thickness=source.thickness;
|
||||
acadrust::EntityType::Circle(circle)
|
||||
} else {
|
||||
let mut arc=source.clone();arc.start_angle=union.start;arc.end_angle=union.end;acadrust::EntityType::Arc(arc)
|
||||
};
|
||||
candidates[a].1=replacement;
|
||||
}
|
||||
changed.insert(candidates[a].0);removed.insert(candidates[b].0);progress=true;
|
||||
continue;
|
||||
}
|
||||
let (acadrust::EntityType::Line(l),acadrust::EntityType::Line(r))=(&left,&right)
|
||||
else {continue;};
|
||||
if l.common!=r.common||l.thickness!=r.thickness||l.normal!=r.normal {continue;}
|
||||
let point=|p:acadrust::types::Vector3|[p.x,p.y,p.z];
|
||||
let Some(union)=cadkernel::space::line_union(
|
||||
[point(l.start),point(l.end)],[point(r.start),point(r.end)],tolerance)
|
||||
else {continue;};
|
||||
let allowed=match union.kind {
|
||||
cadkernel::space::LineUnionKind::Duplicate=>true,
|
||||
cadkernel::space::LineUnionKind::Overlap=>overlap,
|
||||
cadkernel::space::LineUnionKind::EndToEnd=>end_to_end,
|
||||
};
|
||||
if !allowed {continue;}
|
||||
if let acadrust::EntityType::Line(line)=&mut candidates[a].1 {
|
||||
line.start=acadrust::types::Vector3::new(union.start[0],union.start[1],union.start[2]);
|
||||
line.end=acadrust::types::Vector3::new(union.end[0],union.end[1],union.end[2]);
|
||||
}
|
||||
changed.insert(candidates[a].0);removed.insert(candidates[b].0);progress=true;
|
||||
}
|
||||
}
|
||||
if !progress {break;}
|
||||
}
|
||||
if dups.is_empty() {
|
||||
self.command_line
|
||||
.push_output(crate::t!("OVERKILL: no duplicate objects found.").as_ref());
|
||||
if !changed.is_empty()||!removed.is_empty() {
|
||||
self.push_undo_snapshot(i,"OVERKILL");
|
||||
let updates:Vec<_>=candidates.into_iter().filter(|(h,_)|changed.contains(h)&&!removed.contains(h)).collect();
|
||||
let updated_count = updates.len();
|
||||
let mut changes = Vec::new();
|
||||
for (handle, entity) in updates {
|
||||
if let Some(target) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
*target = entity;
|
||||
changes.push((handle, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
let mut handles:Vec<_>=removed.iter().copied().collect();
|
||||
handles.sort_by_key(|handle| handle.value());
|
||||
self.tabs[i].scene.erase_entities(&handles);
|
||||
self.tabs[i].dirty=true;self.refresh_properties();
|
||||
self.command_line.push_output(&format!("OVERKILL: removed {} objects; updated {} objects.",removed.len(),updated_count));
|
||||
} else {
|
||||
let n = dups.len();
|
||||
self.push_undo_snapshot(i, "OVERKILL");
|
||||
self.tabs[i].scene.erase_entities(&dups);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
self.command_line
|
||||
.push_output(crate::tf!("OVERKILL: deleted {n} duplicate object(s).").as_ref());
|
||||
self.command_line.push_output("OVERKILL: no objects required changes.");
|
||||
}
|
||||
}
|
||||
|
||||
// ── PICKADD / PICKDRAG — selection UX (#226, app settings) ───
|
||||
// Bare form reports; `<name> 0|1` sets and persists. Defaults keep
|
||||
// today's behaviour (PICKADD 1, PICKDRAG 0).
|
||||
|
||||
@@ -15,6 +15,7 @@ pub mod mirror;
|
||||
pub mod mledit;
|
||||
pub mod ncopy;
|
||||
pub mod offset;
|
||||
pub mod overkill;
|
||||
pub mod pedit;
|
||||
pub mod block_edit;
|
||||
pub mod refedit;
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::DVec3;
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Settings {
|
||||
pub tolerance: f64,
|
||||
pub ignore: u16,
|
||||
pub optimize: bool,
|
||||
pub overlap: bool,
|
||||
pub end_to_end: bool,
|
||||
pub associativity: bool,
|
||||
}
|
||||
impl Default for Settings {
|
||||
fn default() -> Self { Self { tolerance: 1e-6, ignore: 0, optimize: true,
|
||||
overlap: true, end_to_end: true, associativity: true } }
|
||||
}
|
||||
fn remembered() -> &'static std::sync::Mutex<Settings> {
|
||||
static SETTINGS: std::sync::OnceLock<std::sync::Mutex<Settings>> = std::sync::OnceLock::new();
|
||||
SETTINGS.get_or_init(|| std::sync::Mutex::new(Settings::default()))
|
||||
}
|
||||
enum Step { Selection, Options, Ignore, Tolerance, Optimize, Overlap, EndToEnd, Associativity }
|
||||
pub struct OverkillCommand { selected: Vec<Handle>, step: Step, settings: Settings }
|
||||
impl OverkillCommand {
|
||||
pub fn new(selected: Vec<Handle>) -> Self {
|
||||
let step = if selected.is_empty() { Step::Selection } else { Step::Options };
|
||||
Self { selected, step, settings: *remembered().lock().unwrap_or_else(|e|e.into_inner()) }
|
||||
}
|
||||
fn finish(&self) -> CmdResult {
|
||||
let s = self.settings;
|
||||
*remembered().lock().unwrap_or_else(|e|e.into_inner())=s;
|
||||
CmdResult::Relaunch(format!("OVERKILL_APPLY {} {} {} {} {} {}", s.tolerance,
|
||||
s.ignore, u8::from(s.optimize), u8::from(s.overlap), u8::from(s.end_to_end),
|
||||
u8::from(s.associativity)), self.selected.clone())
|
||||
}
|
||||
}
|
||||
impl CadCommand for OverkillCommand {
|
||||
fn name(&self) -> &'static str { "OVERKILL" }
|
||||
fn prompt(&self) -> String {
|
||||
match self.step {
|
||||
Step::Selection => "Select objects:".into(),
|
||||
Step::Options => format!("Tolerance={} Ignore={} Optimize={} Overlap={} End-to-end={}\nEnter an option [Done/Ignore/tOlerance/optimize Plines/combine parTial overlap/combine Endtoend/Associativity] <Done>:", self.settings.tolerance, self.settings.ignore, self.settings.optimize, self.settings.overlap, self.settings.end_to_end),
|
||||
Step::Ignore => "Properties to ignore [None/All/Color/LAyer/Ltype/ltScale/LWeight/Thickness/TRansparency/plotSTyle/Material]:".into(),
|
||||
Step::Tolerance => format!("Specify tolerance <{}>:", self.settings.tolerance),
|
||||
Step::Optimize => format!("Optimize segments within polylines [Yes/No] <{}>:",if self.settings.optimize{"Yes"}else{"No"}),
|
||||
Step::Overlap => format!("Combine partially overlapping objects [Yes/No] <{}>:",if self.settings.overlap{"Yes"}else{"No"}),
|
||||
Step::EndToEnd => format!("Combine end-to-end objects [Yes/No] <{}>:",if self.settings.end_to_end{"Yes"}else{"No"}),
|
||||
Step::Associativity => format!("Maintain associative objects [Yes/No] <{}>:",if self.settings.associativity{"Yes"}else{"No"}),
|
||||
}
|
||||
}
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
match self.step {
|
||||
Step::Options => [("Done","D"),("Ignore","I"),("Tolerance","O"),("Optimize polylines","P"),("Combine overlap","T"),("Combine end-to-end","E"),("Associativity","A")].into_iter().map(|(l,k)|CmdOption::new(l,k)).collect(),
|
||||
Step::Optimize|Step::Overlap|Step::EndToEnd|Step::Associativity => vec![CmdOption::new("Yes","Y"),CmdOption::new("No","N")],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
fn is_selection_gathering(&self) -> bool { matches!(self.step,Step::Selection) }
|
||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult { self.selected=handles; CmdResult::NeedPoint }
|
||||
fn wants_text_input(&self) -> bool { !self.is_selection_gathering() }
|
||||
fn on_point(&mut self, _:DVec3) -> CmdResult { CmdResult::NeedPoint }
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match self.step {
|
||||
Step::Selection if self.selected.is_empty() => CmdResult::Cancel,
|
||||
Step::Options => self.finish(),
|
||||
_ => { self.step=Step::Options; CmdResult::NeedPoint }
|
||||
}
|
||||
}
|
||||
fn on_text_input(&mut self, text:&str) -> Option<CmdResult> {
|
||||
let k=text.trim().to_ascii_uppercase();
|
||||
match self.step {
|
||||
Step::Options => match k.as_str() {
|
||||
"D"|"DONE" => return Some(self.finish()),
|
||||
"I"|"IGNORE" => self.step=Step::Ignore,
|
||||
"O"|"TOLERANCE" => self.step=Step::Tolerance,
|
||||
"P"|"PLINES" => self.step=Step::Optimize,
|
||||
"T"|"PARTIAL" => self.step=Step::Overlap,
|
||||
"E"|"ENDTOEND" => self.step=Step::EndToEnd,
|
||||
"A"|"ASSOCIATIVITY" => self.step=Step::Associativity,
|
||||
_=>{},
|
||||
},
|
||||
Step::Ignore => {
|
||||
let flag=match k.as_str() { "N"|"NONE"=>{self.settings.ignore=0;0}, "A"|"ALL"=>511,
|
||||
"C"|"COLOR"=>1,"LA"|"LAYER"=>2,"L"|"LTYPE"=>4,"S"|"LTSCALE"=>8,
|
||||
"LW"|"LWEIGHT"=>16,"T"|"THICKNESS"=>32,"TR"|"TRANSPARENCY"=>64,
|
||||
"ST"|"PLOTSTYLE"=>128,"M"|"MATERIAL"=>256,_=>return Some(CmdResult::NeedPoint)};
|
||||
self.settings.ignore|=flag; self.step=Step::Options;
|
||||
}
|
||||
Step::Tolerance => if let Ok(v)=k.parse::<f64>() { if v.is_finite()&&v>=0.0 {
|
||||
self.settings.tolerance=v; self.step=Step::Options;
|
||||
} },
|
||||
Step::Optimize|Step::Overlap|Step::EndToEnd|Step::Associativity => {
|
||||
let yes=match k.as_str(){"Y"|"YES"=>true,"N"|"NO"=>false,_=>return Some(CmdResult::NeedPoint)};
|
||||
match self.step {Step::Optimize=>self.settings.optimize=yes,Step::Overlap=>self.settings.overlap=yes,
|
||||
Step::EndToEnd=>self.settings.end_to_end=yes,Step::Associativity=>self.settings.associativity=yes,_=>{}}
|
||||
self.step=Step::Options;
|
||||
}
|
||||
_=>{},
|
||||
}
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalized(entity:&EntityType, ignore:u16) -> EntityType {
|
||||
let mut e=entity.clone();
|
||||
let common=e.common_mut();
|
||||
let default=acadrust::entities::EntityCommon::new();
|
||||
common.handle=Handle::NULL;
|
||||
if ignore&1!=0 {common.color=default.color;common.color_name=None;common.color_book_handle=None;}
|
||||
if ignore&2!=0 {common.layer.clear();}
|
||||
if ignore&4!=0 {common.linetype.clear();common.linetype_handle=None;}
|
||||
if ignore&8!=0 {common.linetype_scale=1.0;}
|
||||
if ignore&16!=0 {common.line_weight=default.line_weight;}
|
||||
if ignore&64!=0 {common.transparency=default.transparency;}
|
||||
if ignore&128!=0 {common.plotstyle_flags=0;common.plotstyle_handle=None;}
|
||||
if ignore&256!=0 {common.material_flags=0;common.material_handle=None;}
|
||||
if ignore&32!=0 {match &mut e {EntityType::Line(v)=>v.thickness=0.0,
|
||||
EntityType::Circle(v)=>v.thickness=0.0,EntityType::Arc(v)=>v.thickness=0.0,
|
||||
EntityType::LwPolyline(v)=>v.thickness=0.0,_=>{}}}
|
||||
e
|
||||
}
|
||||
|
||||
pub fn optimize(entity:&mut EntityType, tolerance:f64) {
|
||||
if let EntityType::LwPolyline(p)=entity {
|
||||
// Width changes and bulged spans carry geometry that must be retained.
|
||||
if p.vertices.iter().any(|v|v.bulge!=0.0||v.start_width!=0.0||v.end_width!=0.0) {return;}
|
||||
let points:Vec<_>=p.vertices.iter().map(|v|[v.location.x,v.location.y,p.elevation]).collect();
|
||||
let indices=cadkernel::space::simplify_linear_chain(&points,tolerance);
|
||||
if indices.len()>=if p.is_closed{3}else{2} {
|
||||
p.vertices=indices.into_iter().map(|i|p.vertices[i].clone()).collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use acadrust::entities::{Line, LwPolyline};
|
||||
use acadrust::types::{Color, Vector2};
|
||||
|
||||
#[test]
|
||||
fn normalization_ignores_only_the_requested_properties() {
|
||||
let mut first = Line::from_coords(0.0, 0.0, 0.0, 2.0, 0.0, 0.0);
|
||||
first.common.handle = Handle::new(1);
|
||||
first.common.layer = "FIRST".into();
|
||||
first.common.color = Color::from_index(1);
|
||||
let mut second = first.clone();
|
||||
second.common.handle = Handle::new(2);
|
||||
second.common.layer = "SECOND".into();
|
||||
second.common.color = Color::from_index(2);
|
||||
|
||||
assert_ne!(normalized(&EntityType::Line(first.clone()), 0), normalized(&EntityType::Line(second.clone()), 0));
|
||||
assert_eq!(normalized(&EntityType::Line(first), 1 | 2), normalized(&EntityType::Line(second), 1 | 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polyline_optimization_removes_only_plain_collinear_vertices() {
|
||||
let mut plain = EntityType::LwPolyline(LwPolyline::from_points(vec![
|
||||
Vector2::new(0.0, 0.0),
|
||||
Vector2::new(1.0, 0.0),
|
||||
Vector2::new(2.0, 0.0),
|
||||
]));
|
||||
optimize(&mut plain, 1e-9);
|
||||
let EntityType::LwPolyline(plain) = plain else { unreachable!() };
|
||||
assert_eq!(plain.vertices.len(), 2);
|
||||
|
||||
let mut styled = LwPolyline::from_points(vec![
|
||||
Vector2::new(0.0, 0.0),
|
||||
Vector2::new(1.0, 0.0),
|
||||
Vector2::new(2.0, 0.0),
|
||||
]);
|
||||
styled.vertices[1].start_width = 1.0;
|
||||
let mut styled = EntityType::LwPolyline(styled);
|
||||
optimize(&mut styled, 1e-9);
|
||||
let EntityType::LwPolyline(styled) = styled else { unreachable!() };
|
||||
assert_eq!(styled.vertices.len(), 3);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user