Standardize parametric constraints and interoperability

This commit is contained in:
Hakan Seven committed 2026-09-15 03:19:52 +03:00
1 parent 753df67ee4
commit d738ebfcc6
41 files changed
+10038 -7847

No files matched your search

+135 -38
View File
@@ -48,16 +48,20 @@ use acadrust::{CadDocument, DxfReader, DxfWriter, EntityType};
use glam::{DVec3, Mat4, Vec3};
use iced::{Color, Point, Rectangle};
use OpenCADStudio::scene::parametric_constraints::{
ConstraintKind, ParametricRef, ParametricScope,
};
use OpenCADStudio::scene::pick::hit_test::{box_hit, click_hit};
use OpenCADStudio::scene::pick::interaction_index::InteractionIndex;
use OpenCADStudio::scene::pick::selection_state::SelectionState;
use OpenCADStudio::scene::pipeline::wire_arena::partition_wires;
use OpenCADStudio::scene::sketch_constraints::{ConstraintKind, SketchRef, SketchScope};
use OpenCADStudio::scene::view::camera::Camera;
use OpenCADStudio::scene::{ChangeKind, Scene};
use OpenCADStudio::snap::Snapper;
use OpenCADStudio::ui::icons::{self, CHECK};
use OpenCADStudio::ui::overlay::{grid_segments, GridCanvasState, GridKey, GridParams, GridStyle, should_reuse};
use OpenCADStudio::ui::overlay::{
grid_segments, should_reuse, GridCanvasState, GridKey, GridParams, GridStyle,
};
use OpenCADStudio::ui::properties::LinetypeItem;
use OpenCADStudio::ui::ribbon::{LayerInfo, Ribbon};
@@ -109,8 +113,8 @@ impl Default for BenchmarkRunner {
impl BenchmarkRunner {
pub fn new() -> Self {
let args: Vec<String> = env::args().collect();
let quick_mode = args.iter().any(|a| a == "--quick" || a == "-q")
|| env::var("CAD_BENCH_QUICK").is_ok();
let quick_mode =
args.iter().any(|a| a == "--quick" || a == "-q") || env::var("CAD_BENCH_QUICK").is_ok();
let filter = args
.windows(2)
@@ -220,22 +224,37 @@ impl BenchmarkRunner {
/// Print summary table to stdout and persist JSON report.
pub fn finish(&self) {
let baseline_map: HashMap<String, BenchmarkMetric> = if let Some(ref path) = self.baseline_path {
if let Ok(content) = std::fs::read_to_string(path) {
if let Ok(report) = serde_json::from_str::<BenchmarkSuiteReport>(&content) {
println!("\nLoaded baseline from '{}' ({} metrics)", path.display(), report.metrics.len());
report.metrics.into_iter().map(|m| (m.name.clone(), m)).collect()
let baseline_map: HashMap<String, BenchmarkMetric> =
if let Some(ref path) = self.baseline_path {
if let Ok(content) = std::fs::read_to_string(path) {
if let Ok(report) = serde_json::from_str::<BenchmarkSuiteReport>(&content) {
println!(
"\nLoaded baseline from '{}' ({} metrics)",
path.display(),
report.metrics.len()
);
report
.metrics
.into_iter()
.map(|m| (m.name.clone(), m))
.collect()
} else {
eprintln!(
"Warning: Failed to parse baseline JSON at '{}'",
path.display()
);
HashMap::new()
}
} else {
eprintln!("Warning: Failed to parse baseline JSON at '{}'", path.display());
eprintln!(
"Warning: Could not read baseline file at '{}'",
path.display()
);
HashMap::new()
}
} else {
eprintln!("Warning: Could not read baseline file at '{}'", path.display());
HashMap::new()
}
} else {
HashMap::new()
};
};
println!("\n========================================================================================================================");
println!(" OPENCADSTUDIO PERFORMANCE BENCHMARK REPORT ");
@@ -301,7 +320,11 @@ impl BenchmarkRunner {
timestamp: chrono_now(),
os: env::consts::OS.to_string(),
arch: env::consts::ARCH.to_string(),
profile: if cfg!(debug_assertions) { "debug".to_string() } else { "release".to_string() },
profile: if cfg!(debug_assertions) {
"debug".to_string()
} else {
"release".to_string()
},
is_quick_mode: self.quick_mode,
metrics: self.results.clone(),
};
@@ -311,9 +334,16 @@ impl BenchmarkRunner {
let _ = std::fs::create_dir_all(parent);
}
if let Err(e) = std::fs::write(&self.output_path, json) {
eprintln!("Error saving metrics to '{}': {}", self.output_path.display(), e);
eprintln!(
"Error saving metrics to '{}': {}",
self.output_path.display(),
e
);
} else {
println!("Saved complete performance metrics JSON to: {}\n", self.output_path.display());
println!(
"Saved complete performance metrics JSON to: {}\n",
self.output_path.display()
);
}
}
}
@@ -381,9 +411,27 @@ fn bench_scene_entity_ingestion(runner: &mut BenchmarkRunner) {
let y = (i / 50) as f64 * 50.0 + 6000.0;
let mut pl = LwPolyline::new();
pl.vertices = vec![
LwVertex { location: Vector2::new(x, y), bulge: 0.0, start_width: 0.0, end_width: 0.0, vertex_id: 0 },
LwVertex { location: Vector2::new(x + 20.0, y), bulge: 0.5, start_width: 0.0, end_width: 0.0, vertex_id: 1 },
LwVertex { location: Vector2::new(x + 20.0, y + 20.0), bulge: 0.0, start_width: 0.0, end_width: 0.0, vertex_id: 2 },
LwVertex {
location: Vector2::new(x, y),
bulge: 0.0,
start_width: 0.0,
end_width: 0.0,
vertex_id: 0,
},
LwVertex {
location: Vector2::new(x + 20.0, y),
bulge: 0.5,
start_width: 0.0,
end_width: 0.0,
vertex_id: 1,
},
LwVertex {
location: Vector2::new(x + 20.0, y + 20.0),
bulge: 0.0,
start_width: 0.0,
end_width: 0.0,
vertex_id: 2,
},
];
scene.add_entity(EntityType::LwPolyline(pl));
}
@@ -462,8 +510,20 @@ fn bench_analytical_tessellation_zoom(runner: &mut BenchmarkRunner) {
let mut pline = LwPolyline::new();
pline.is_closed = true;
pline.vertices = vec![
LwVertex { location: Vector2::new(x, y), bulge: 1.0, start_width: 0.0, end_width: 0.0, vertex_id: 0 },
LwVertex { location: Vector2::new(x + 30.0, y), bulge: 1.0, start_width: 0.0, end_width: 0.0, vertex_id: 1 },
LwVertex {
location: Vector2::new(x, y),
bulge: 1.0,
start_width: 0.0,
end_width: 0.0,
vertex_id: 0,
},
LwVertex {
location: Vector2::new(x + 30.0, y),
bulge: 1.0,
start_width: 0.0,
end_width: 0.0,
vertex_id: 1,
},
];
scene.add_entity(EntityType::LwPolyline(pline));
}
@@ -610,7 +670,12 @@ fn bench_hit_test_picking(runner: &mut BenchmarkRunner) {
let view_rot = Mat4::IDENTITY;
let eye = DVec3::new(1000.0, 1000.0, 2000.0);
let bounds = Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 1080.0 };
let bounds = Rectangle {
x: 0.0,
y: 0.0,
width: 1920.0,
height: 1080.0,
};
let n_queries = if runner.quick_mode { 200 } else { 1_000 };
let mut click_samples = Vec::with_capacity(5);
@@ -620,7 +685,15 @@ fn bench_hit_test_picking(runner: &mut BenchmarkRunner) {
for q in 0..n_queries {
let cx = ((q * 37) % 1920) as f32;
let cy = ((q * 53) % 1080) as f32;
let hit = click_hit(Point::new(cx, cy), wires.as_slice(), view_rot, eye, bounds, false, 8.0);
let hit = click_hit(
Point::new(cx, cy),
wires.as_slice(),
view_rot,
eye,
bounds,
false,
8.0,
);
black_box(hit);
}
let per_query_us = (t0.elapsed().as_secs_f64() * 1_000_000.0) / (n_queries as f64);
@@ -716,7 +789,12 @@ fn bench_osnap_evaluation(runner: &mut BenchmarkRunner) {
let view_rot = Mat4::IDENTITY;
let eye = DVec3::new(500.0, 500.0, 1000.0);
let bounds = Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 1080.0 };
let bounds = Rectangle {
x: 0.0,
y: 0.0,
width: 1920.0,
height: 1080.0,
};
let grid_origin = Vec3::ZERO;
let grid_axes = (Vec3::X, Vec3::Y, Vec3::Z);
@@ -729,10 +807,7 @@ fn bench_osnap_evaluation(runner: &mut BenchmarkRunner) {
let wx = (q as f64 * 7.3) % 2500.0;
let wy = (q as f64 * 11.7) % 2500.0;
let cursor_world = DVec3::new(wx, wy, 0.0);
let cursor_screen = Point::new(
((q * 23) % 1920) as f32,
((q * 31) % 1080) as f32,
);
let cursor_screen = Point::new(((q * 23) % 1920) as f32, ((q * 31) % 1080) as f32);
let hit = snapper.snap(
cursor_world,
@@ -798,11 +873,23 @@ fn bench_geometric_constraint_solving(runner: &mut BenchmarkRunner) {
lines.push((h1, h2, h3));
}
let cs = scene.sketch_constraint_set_mut(SketchScope::ModelSpace);
let cs = scene.parametric_constraint_set_mut(ParametricScope::ModelSpace);
for &(h1, h2, h3) in &lines {
cs.add(ConstraintKind::Perpendicular, vec![SketchRef::whole(h1), SketchRef::whole(h2)], None);
cs.add(ConstraintKind::Perpendicular, vec![SketchRef::whole(h2), SketchRef::whole(h3)], None);
cs.add(ConstraintKind::Parallel, vec![SketchRef::whole(h1), SketchRef::whole(h3)], None);
cs.add(
ConstraintKind::Perpendicular,
vec![ParametricRef::whole(h1), ParametricRef::whole(h2)],
None,
);
cs.add(
ConstraintKind::Perpendicular,
vec![ParametricRef::whole(h2), ParametricRef::whole(h3)],
None,
);
cs.add(
ConstraintKind::Parallel,
vec![ParametricRef::whole(h1), ParametricRef::whole(h3)],
None,
);
}
let target = lines[run_idx % lines.len()].0;
@@ -858,7 +945,9 @@ fn bench_dxf_io_throughput(runner: &mut BenchmarkRunner) {
for _ in 0..runs {
let t0 = Instant::now();
let bytes = DxfWriter::new(&doc).write_to_vec().expect("DXF write succeeds");
let bytes = DxfWriter::new(&doc)
.write_to_vec()
.expect("DXF write succeeds");
write_samples.push(t0.elapsed().as_secs_f64() * 1000.0);
dxf_bytes = bytes;
}
@@ -1095,8 +1184,12 @@ fn bench_ui_grid_geometry(runner: &mut BenchmarkRunner) {
// ── 1. Uncached grid geometry generation ──
if runner.should_run("ui_grid_geometry_uncached") {
for _ in 0..10 {
let _ = black_box(grid_segments(view_rot1, eye1, bounds1, step1, origin1, axes1, limits1));
let _ = black_box(grid_segments(view_rot2, eye2, bounds2, step2, origin2, axes2, limits2));
let _ = black_box(grid_segments(
view_rot1, eye1, bounds1, step1, origin1, axes1, limits1,
));
let _ = black_box(grid_segments(
view_rot2, eye2, bounds2, step2, origin2, axes2, limits2,
));
}
let n = if runner.quick_mode { 20 } else { 100 };
@@ -1172,7 +1265,11 @@ fn bench_ui_grid_geometry(runner: &mut BenchmarkRunner) {
let t0 = Instant::now();
let mut hit_count = 0u32;
for _ in 0..n {
let key = GridKey::from_grids(black_box(&grids), black_box(canvas_bounds), GridStyle::default());
let key = GridKey::from_grids(
black_box(&grids),
black_box(canvas_bounds),
GridStyle::default(),
);
if should_reuse(state.key.borrow().as_ref(), &key) {
hit_count += 1;
}