SWEEP: sweep from the picked path end; kernel run faces and placed ends

- An open path is swept from its end nearest the pick point (in plan), as
  the reference does; the record keeps the original path and the placed
  frame marks the start.
- Kernel from ramox81/cadkernel 1df75f5: one face per path run for
  twisted, scaled and spline sweeps, placed records swept from the placed
  end, end caps without pcurves.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-07 16:34:34 +03:00
1 parent 41d93b6f54
commit 0c3ca96822
4 files changed
+56 -1

No files matched your search

+4
View File
@@ -181,3 +181,7 @@ harness = false
[[test]]
name = "integration"
path = "tests/integration.rs"
[patch."https://github.com/HakanSeven12/opencadkernel.git"]
opencadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "1df75f5" }
opencadkernel-constraints = { git = "https://github.com/ramox81/cadkernel.git", rev = "1df75f5" }
+4
View File
@@ -1356,6 +1356,9 @@ pub struct SweepOptions {
pub scale: f64,
/// Total twist along the path, in radians.
pub twist_angle: f64,
/// Where the path was picked: an open path is swept from its end nearest
/// this point (seen from above), as the reference does.
pub path_pick: Option<DVec3>,
}
impl Default for SweepOptions {
@@ -1366,6 +1369,7 @@ impl Default for SweepOptions {
base_point: None,
scale: 1.0,
twist_angle: 0.0,
path_pick: None,
}
}
}
+2 -1
View File
@@ -1822,7 +1822,7 @@ impl CadCommand for SweepCommand {
self.step == SweepStep::PickPath
}
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult {
if self.step != SweepStep::PickPath || handle.is_null() || self.contains_profile(handle) {
self.injected_path = None;
return CmdResult::NeedPoint;
@@ -1835,6 +1835,7 @@ impl CadCommand for SweepCommand {
let Some(path) = path.filter(crate::scene::model::sweep_model::is_sweep_path) else {
return CmdResult::NeedPoint;
};
self.options.path_pick = pt.is_finite().then_some(pt);
if (self.options.scale - 1.0).abs() > 1e-12 {
if let Some(message) = crate::scene::model::sweep_model::scaled_sweep_path_refusal(&path) {
return CmdResult::ReportMeasurement(format!(
+46
View File
@@ -112,7 +112,53 @@ pub fn sweep_selection_options(profiles: &[EntityType], mut options: SweepOption
Some(options)
}
/// The path traversed the other way when it was picked nearer its end
/// (measured in plan, as the reference measures the pick), else `None`.
fn reversed_toward_pick(path: &EntityType, pick: glam::DVec3) -> Option<EntityType> {
let (start, end) = match path {
EntityType::Polyline3D(value) if !value.is_closed() => {
let p = |v: &codec::entities::Vertex3DPolyline| glam::DVec3::new(v.position.x, v.position.y, v.position.z);
(p(value.vertices.first()?), p(value.vertices.last()?))
}
EntityType::Spline(value) if !value.flags.closed => {
let points = if value.control_points.is_empty() { &value.fit_points } else { &value.control_points };
let p = |v: &codec::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
(p(points.first()?), p(points.last()?))
}
_ => {
let planar = crate::entities::curve::entity_curve(path)?;
if planar.curve.is_closed() { return None; }
let at = |t: f64| glam::DVec3::from_array(planar.plane.point_at(planar.curve.point_at(t)));
(at(0.0), at(1.0))
}
};
let flat = |p: glam::DVec3| p.truncate().distance(pick.truncate());
if flat(end) >= flat(start) { return None; }
match path {
// An arc has one direction; reversed it is a one-segment polyline.
EntityType::Arc(arc) => {
let mut polyline = codec::LwPolyline::new();
let sweep = (arc.end_angle - arc.start_angle).rem_euclid(std::f64::consts::TAU);
let at = |angle: f64| codec::types::Vector2::new(arc.center.x + arc.radius * angle.cos(), arc.center.y + arc.radius * angle.sin());
let mut first = codec::entities::LwVertex::new(at(arc.end_angle));
first.bulge = -(sweep / 4.0).tan();
polyline.vertices = vec![first, codec::entities::LwVertex::new(at(arc.start_angle))];
polyline.normal = arc.normal;
polyline.elevation = arc.center.z;
Some(EntityType::LwPolyline(polyline))
}
_ => crate::modules::draw::modify::reverse::ReverseCommand::reversed(path),
}
}
pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptions) -> Option<SolidHistorySweep> {
// Placed from the picked end; the record keeps the original path, and
// the placed frame tells which end the sweep starts from.
if let Some(reversed) = options.path_pick.and_then(|pick| reversed_toward_pick(path, pick)) {
let mut record = sweep_record(profile, &reversed, SweepOptions { path_pick: None, ..options })?;
record.path_entity = Some(embedded_sweep_path(path)?);
return Some(record);
}
let (sweep_entity, sweep_entity_transform) = embedded_sweep_profile(profile)?;
let (plane, wires, _) = kernel::acis::sweep_profile_geometry(&sweep_entity, sweep_entity_transform).ok()?;
let base_point = match options.base_point {