VPCLIP arc options, visible linked clip boundaries, XCLIP depth rejection

- VPCLIP arc mode takes Angle, CEnter, Direction, Radius and Second pt with
  the reference prompts, and CLose closes with a tangent arc; the arc math
  is shared with PLINE.
- Viewport clip boundaries from MVIEW and VPCLIP stay visible; picking or
  erasing a boundary takes its viewport along.
- XCLIP Clipdepth: a rejected front plane keeps the old front plane and
  clears the back plane.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-25 12:22:44 +03:00
1 parent bd17e7e0d4
commit 2e397aec99
5 files changed
+329 -163

No files matched your search

+5 -13
View File
@@ -82,10 +82,7 @@ impl OpenCADStudio {
let old = vp.clip_boundary_handle;
let scene = &mut self.tabs[i].scene;
let clip = match boundary {
Some(mut entity) => {
entity.common_mut().invisible = true;
scene.add_entity(entity)
}
Some(entity) => scene.add_entity(entity),
None => boundary_handle,
};
let mut vp = vp;
@@ -116,7 +113,6 @@ impl OpenCADStudio {
vp.clip_boundary_handle = clip;
if let Some(entity) = scene.document.get_entity_mut(clip) {
let common = entity.common_mut();
common.invisible = true;
if !common.reactors.contains(&viewport) {
common.reactors.push(viewport);
}
@@ -182,13 +178,10 @@ impl OpenCADStudio {
let label = self.history_label_from_active_cmd(i, "MVIEW");
let pending = self.begin_undo(i, label, touched, true);
let clip_handle = match boundary {
Some(mut boundary) => {
// A non-rectangular viewport owns a helper boundary
// entity through `clip_boundary_handle`. Keep that
// helper in the document for DWG compatibility and
// stencil clipping, but do not expose it as a separate
// selectable polyline.
boundary.common_mut().invisible = true;
Some(boundary) => {
// A non-rectangular viewport owns its boundary through
// `clip_boundary_handle`: a visible paper-space object that
// is picked and erased together with the viewport.
match self.commit_entity_handle(boundary) {
Some(handle) => handle,
None => {
@@ -242,7 +235,6 @@ impl OpenCADStudio {
}
if let Some(boundary) = self.tabs[i].scene.document.get_entity_mut(clip_handle) {
let common = boundary.common_mut();
common.invisible = true;
if !common.reactors.contains(&viewport_handle) {
common.reactors.push(viewport_handle);
}
+150 -145
View File
@@ -47,7 +47,7 @@ enum SegMode {
/// sub-step returns to `None` once it has produced a vertex, or when Undo /
/// Enter / Escape backs out of it.
#[derive(Clone, Copy, PartialEq, Debug)]
enum Sub {
pub(crate) enum Sub {
None,
/// Width / Halfwidth: starting width, then ending width. `half` scales the
/// prompt and the typed value by two.
@@ -81,7 +81,7 @@ enum Sub {
impl Sub {
/// The step reads a single typed value (width, length, angle, radius).
fn is_scalar(self) -> bool {
pub(crate) fn is_scalar(self) -> bool {
matches!(
self,
Sub::WidthStart { .. }
@@ -97,7 +97,7 @@ impl Sub {
}
/// The typed value is an angle (degrees) rather than a length.
fn is_angle(self) -> bool {
pub(crate) fn is_angle(self) -> bool {
matches!(
self,
Sub::ArcAngle | Sub::ArcCenterAngle { .. } | Sub::ArcRadiusAngle { .. }
@@ -249,149 +249,8 @@ impl PlineCommand {
.unwrap_or(DVec2::X)
}
/// Sign for an arc whose direction is not fixed by its definition
/// (CEnter / Radius endpoints, chord length): bend the way the previous
/// segment was heading so the polyline flows on, else counter-clockwise.
fn free_arc_sign(&self, a: DVec2, e: DVec2) -> f64 {
let chord = e - a;
let cross = match self.last_tangent {
Some(t) => t.as_dvec2().perp_dot(chord),
None => 1.0,
};
if cross < 0.0 {
-1.0
} else {
1.0
}
}
/// Bulge of the arc from `a` to `e` through `s` (3-point arc). `None` when
/// the points are collinear or coincident.
fn bulge_through(a: DVec2, s: DVec2, e: DVec2) -> Option<f64> {
let d = 2.0 * (a.x * (s.y - e.y) + s.x * (e.y - a.y) + e.x * (a.y - s.y));
if d.abs() < 1e-12 {
return None;
}
let a2 = a.length_squared();
let s2 = s.length_squared();
let e2 = e.length_squared();
let cx = (a2 * (s.y - e.y) + s2 * (e.y - a.y) + e2 * (a.y - s.y)) / d;
let cy = (a2 * (e.x - s.x) + s2 * (a.x - e.x) + e2 * (s.x - a.x)) / d;
let c = DVec2::new(cx, cy);
let start = (a - c).y.atan2((a - c).x);
let end = (e - c).y.atan2((e - c).x);
// The arc bends towards `s`: with `s` right of the chord a → e the
// arc runs counter-clockwise, left of it clockwise.
let ccw = (e - a).perp_dot(s - a) < 0.0;
let mut sweep = end - start;
if ccw {
if sweep <= 0.0 {
sweep += std::f64::consts::TAU;
}
} else if sweep >= 0.0 {
sweep -= std::f64::consts::TAU;
}
Some((sweep / 4.0).tan())
}
/// Endpoint of the arc about `c` starting at `a` with signed `sweep`.
fn rotate_about(c: DVec2, a: DVec2, sweep: f64) -> DVec2 {
let (sn, cs) = sweep.sin_cos();
let v = a - c;
c + DVec2::new(v.x * cs - v.y * sn, v.x * sn + v.y * cs)
}
/// Resolve the arc a sub-step describes for a candidate `cursor`/point:
/// `(endpoint, bulge)`. `None` when the geometry is degenerate for that
/// input (e.g. a chord longer than the diameter).
fn arc_for(&self, sub: Sub, a: DVec2, p: DVec2) -> Option<(DVec2, f64)> {
let sweep_ok = |s: f64| s.abs() > 1e-9 && s.abs() < std::f64::consts::TAU;
match sub {
Sub::ArcAngleEnd { angle } if sweep_ok(angle) => {
if (p - a).length_squared() < 1e-12 {
return None;
}
Some((p, (angle / 4.0).tan()))
}
Sub::ArcAngleCenter { angle } if sweep_ok(angle) => {
if (a - p).length_squared() < 1e-12 {
return None;
}
Some((Self::rotate_about(p, a, angle), (angle / 4.0).tan()))
}
Sub::ArcAngleRadiusDir { angle, r } if sweep_ok(angle) && r > 0.0 => {
let dir = (p - a).try_normalize()?;
let chord = 2.0 * r * (angle.abs() * 0.5).sin();
Some((a + dir * chord, (angle / 4.0).tan()))
}
Sub::ArcCenterEnd { c } => {
let r = (a - c).length();
if r < 1e-9 {
return None;
}
let dir = (p - c).try_normalize()?;
let e = c + dir * r;
let start = (a - c).y.atan2((a - c).x);
let end = (e - c).y.atan2((e - c).x);
let sign = self.free_arc_sign(a, e);
let mut sweep = (end - start).rem_euclid(std::f64::consts::TAU);
if sign < 0.0 {
sweep -= std::f64::consts::TAU;
}
if !sweep_ok(sweep) {
return None;
}
Some((e, (sweep / 4.0).tan()))
}
Sub::ArcCenterAngle { c } => {
// `p` carries the typed angle in `x`.
let angle = p.x;
if (a - c).length_squared() < 1e-12 || !sweep_ok(angle) {
return None;
}
Some((Self::rotate_about(c, a, angle), (angle / 4.0).tan()))
}
Sub::ArcCenterLength { c } => {
// `p` carries the typed chord length in `x`.
let len = p.x;
let r = (a - c).length();
if r < 1e-9 || len <= 0.0 || len > 2.0 * r {
return None;
}
let sweep = 2.0 * (len / (2.0 * r)).asin();
// Minor arc; bend on the side the previous segment favours.
let e_ccw = Self::rotate_about(c, a, sweep);
let sign = self.free_arc_sign(a, e_ccw);
let sweep = sweep * sign;
Some((Self::rotate_about(c, a, sweep), (sweep / 4.0).tan()))
}
Sub::ArcDirectionEnd { dir } => {
if (p - a).length_squared() < 1e-12 {
return None;
}
Some((p, compute_bulge(a, dir, p)))
}
Sub::ArcRadiusEnd { r } if r > 0.0 => {
let chord = (p - a).length();
if chord < 1e-9 || chord > 2.0 * r {
return None;
}
let sweep = 2.0 * (chord / (2.0 * r)).asin() * self.free_arc_sign(a, p);
Some((p, (sweep / 4.0).tan()))
}
Sub::ArcRadiusAngleDir { r, angle } if r > 0.0 && sweep_ok(angle) => {
let dir = (p - a).try_normalize()?;
let chord = 2.0 * r * (angle.abs() * 0.5).sin();
Some((a + dir * chord, (angle / 4.0).tan()))
}
Sub::ArcSecondEnd { s } => {
if (p - a).length_squared() < 1e-12 {
return None;
}
Some((p, Self::bulge_through(a, s, p)?))
}
_ => None,
}
arc_for(sub, a, p, self.last_tangent)
}
/// Land a segment ending at local `e` with `bulge`, publish it, and
@@ -582,6 +441,152 @@ impl PlineCommand {
// ── Arc geometry helpers ───────────────────────────────────────────────────
/// Sign for an arc whose direction is not fixed by its definition
/// (CEnter / Radius endpoints, chord length): bend the way the previous
/// segment was heading so the polyline flows on, else counter-clockwise.
fn free_arc_sign(last_tangent: Option<Vec2>, a: DVec2, e: DVec2) -> f64 {
let chord = e - a;
let cross = match last_tangent {
Some(t) => t.as_dvec2().perp_dot(chord),
None => 1.0,
};
if cross < 0.0 {
-1.0
} else {
1.0
}
}
/// Bulge of the arc from `a` to `e` through `s` (3-point arc). `None` when
/// the points are collinear or coincident.
fn bulge_through(a: DVec2, s: DVec2, e: DVec2) -> Option<f64> {
let d = 2.0 * (a.x * (s.y - e.y) + s.x * (e.y - a.y) + e.x * (a.y - s.y));
if d.abs() < 1e-12 {
return None;
}
let a2 = a.length_squared();
let s2 = s.length_squared();
let e2 = e.length_squared();
let cx = (a2 * (s.y - e.y) + s2 * (e.y - a.y) + e2 * (a.y - s.y)) / d;
let cy = (a2 * (e.x - s.x) + s2 * (a.x - e.x) + e2 * (s.x - a.x)) / d;
let c = DVec2::new(cx, cy);
let start = (a - c).y.atan2((a - c).x);
let end = (e - c).y.atan2((e - c).x);
// The arc bends towards `s`: with `s` right of the chord a → e the
// arc runs counter-clockwise, left of it clockwise.
let ccw = (e - a).perp_dot(s - a) < 0.0;
let mut sweep = end - start;
if ccw {
if sweep <= 0.0 {
sweep += std::f64::consts::TAU;
}
} else if sweep >= 0.0 {
sweep -= std::f64::consts::TAU;
}
Some((sweep / 4.0).tan())
}
/// Endpoint of the arc about `c` starting at `a` with signed `sweep`.
fn rotate_about(c: DVec2, a: DVec2, sweep: f64) -> DVec2 {
let (sn, cs) = sweep.sin_cos();
let v = a - c;
c + DVec2::new(v.x * cs - v.y * sn, v.x * sn + v.y * cs)
}
/// Resolve the arc an arc sub-step describes (PLINE, VPCLIP) from `a` for a
/// candidate `cursor`/point `p`, after a segment leaving along
/// `last_tangent`: `(endpoint, bulge)`. `None` when the geometry is
/// degenerate for that input (e.g. a chord longer than the diameter).
pub(crate) fn arc_for(sub: Sub, a: DVec2, p: DVec2, last_tangent: Option<Vec2>) -> Option<(DVec2, f64)> {
let sweep_ok = |s: f64| s.abs() > 1e-9 && s.abs() < std::f64::consts::TAU;
match sub {
Sub::ArcAngleEnd { angle } if sweep_ok(angle) => {
if (p - a).length_squared() < 1e-12 {
return None;
}
Some((p, (angle / 4.0).tan()))
}
Sub::ArcAngleCenter { angle } if sweep_ok(angle) => {
if (a - p).length_squared() < 1e-12 {
return None;
}
Some((rotate_about(p, a, angle), (angle / 4.0).tan()))
}
Sub::ArcAngleRadiusDir { angle, r } if sweep_ok(angle) && r > 0.0 => {
let dir = (p - a).try_normalize()?;
let chord = 2.0 * r * (angle.abs() * 0.5).sin();
Some((a + dir * chord, (angle / 4.0).tan()))
}
Sub::ArcCenterEnd { c } => {
let r = (a - c).length();
if r < 1e-9 {
return None;
}
let dir = (p - c).try_normalize()?;
let e = c + dir * r;
let start = (a - c).y.atan2((a - c).x);
let end = (e - c).y.atan2((e - c).x);
let sign = free_arc_sign(last_tangent, a, e);
let mut sweep = (end - start).rem_euclid(std::f64::consts::TAU);
if sign < 0.0 {
sweep -= std::f64::consts::TAU;
}
if !sweep_ok(sweep) {
return None;
}
Some((e, (sweep / 4.0).tan()))
}
Sub::ArcCenterAngle { c } => {
// `p` carries the typed angle in `x`.
let angle = p.x;
if (a - c).length_squared() < 1e-12 || !sweep_ok(angle) {
return None;
}
Some((rotate_about(c, a, angle), (angle / 4.0).tan()))
}
Sub::ArcCenterLength { c } => {
// `p` carries the typed chord length in `x`.
let len = p.x;
let r = (a - c).length();
if r < 1e-9 || len <= 0.0 || len > 2.0 * r {
return None;
}
let sweep = 2.0 * (len / (2.0 * r)).asin();
// Minor arc; bend on the side the previous segment favours.
let e_ccw = rotate_about(c, a, sweep);
let sign = free_arc_sign(last_tangent, a, e_ccw);
let sweep = sweep * sign;
Some((rotate_about(c, a, sweep), (sweep / 4.0).tan()))
}
Sub::ArcDirectionEnd { dir } => {
if (p - a).length_squared() < 1e-12 {
return None;
}
Some((p, compute_bulge(a, dir, p)))
}
Sub::ArcRadiusEnd { r } if r > 0.0 => {
let chord = (p - a).length();
if chord < 1e-9 || chord > 2.0 * r {
return None;
}
let sweep = 2.0 * (chord / (2.0 * r)).asin() * free_arc_sign(last_tangent, a, p);
Some((p, (sweep / 4.0).tan()))
}
Sub::ArcRadiusAngleDir { r, angle } if r > 0.0 && sweep_ok(angle) => {
let dir = (p - a).try_normalize()?;
let chord = 2.0 * r * (angle.abs() * 0.5).sin();
Some((a + dir * chord, (angle / 4.0).tan()))
}
Sub::ArcSecondEnd { s } => {
if (p - a).length_squared() < 1e-12 {
return None;
}
Some((p, bulge_through(a, s, p)?))
}
_ => None,
}
}
/// Compute the bulge for the arc from `a` to `b` that is tangent to `tangent` at `a`.
/// Returns 0.0 if the points are coincident or the tangent is parallel to the chord.
pub(crate) fn compute_bulge(a: DVec2, tangent: DVec2, b: DVec2) -> f64 {
+3 -2
View File
@@ -220,10 +220,11 @@ impl XclipCommand {
self.step = Step::Back;
CmdResult::NeedPoint
}
// A front plane behind the back one changes nothing.
// A front plane behind the back one is not applied; the back
// plane is cleared and the old front one kept.
_ if self.front.is_some_and(|front| front < d) => self.act(XclipAction::Depth {
front: None,
back: None,
back: Some(None),
rejected: true,
}),
_ => self.act(XclipAction::Depth { front: Some(self.front), back: Some(Some(d)), rejected: false }),
+158 -3
View File
@@ -11,6 +11,8 @@
// Enter an arc boundary option
// [Angle/CEnter/CLose/Direction/Line/Radius/Second pt/Undo/Endpoint of arc] <Endpoint>:
// Specify length of line:
// and the arc options' prompts (Angle, CEnter, Direction, Radius, Second pt)
// as the reference words them; CLose in arc mode closes with a tangent arc.
use codec::entities::{LwPolyline, LwVertex, Viewport};
use codec::tables::View;
@@ -20,7 +22,7 @@ use crate::t;
use crate::command::{CadCommand, CmdOption, CmdResult, InputKind};
use crate::modules::draw::draw::polyline::{
arc_sample_points, compute_bulge, seg_exit_tangent, update_tangent_after_arc,
arc_for, arc_sample_points, compute_bulge, seg_exit_tangent, update_tangent_after_arc, Sub,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -78,6 +80,8 @@ pub struct MviewCommand {
/// already has a boundary.
clip: Option<(Handle, bool)>,
picked: Option<EntityType>,
/// VPCLIP arc mode: the arc option being answered.
arc_sub: Sub,
}
impl MviewCommand {
@@ -99,6 +103,7 @@ impl MviewCommand {
original_layout,
clip: None,
picked: None,
arc_sub: Sub::None,
}
}
@@ -122,7 +127,129 @@ impl MviewCommand {
self.clip.map_or(Handle::NULL, |(handle, _)| handle)
}
/// VPCLIP: an arc segment from the last vertex to `e`.
fn push_arc(&mut self, e: DVec2, bulge: f64) -> CmdResult {
let Some(last) = self.polygon.last().copied() else {
return CmdResult::NeedPoint;
};
let end = DVec3::new(e.x, e.y, last.z);
let index = self.polygon.len() - 1;
self.polygon_bulges[index] = bulge;
self.polygon_last_tangent = seg_exit_tangent(last, end, bulge);
self.arc_sub = Sub::None;
self.polygon.push(end);
self.polygon_bulges.push(0.0);
CmdResult::NeedPoint
}
fn last2(&self) -> Option<DVec2> {
self.polygon.last().map(|p| DVec2::new(p.x, p.y))
}
/// VPCLIP arc option answered with a typed value.
fn arc_value(&mut self, value: f64) -> Option<CmdResult> {
let a = self.last2()?;
let tangent = self.polygon_last_tangent;
let angle_ok = |r: f64| r.abs() > 1e-9 && r.abs() < std::f64::consts::TAU;
let next = match self.arc_sub {
Sub::ArcAngle if angle_ok(value.to_radians()) => Sub::ArcAngleEnd { angle: value.to_radians() },
Sub::ArcAngleRadius { angle } if value > 0.0 => Sub::ArcAngleRadiusDir { angle, r: value },
Sub::ArcRadius if value > 0.0 => Sub::ArcRadiusEnd { r: value },
Sub::ArcRadiusAngle { r } if angle_ok(value.to_radians()) => {
Sub::ArcRadiusAngleDir { r, angle: value.to_radians() }
}
sub @ Sub::ArcCenterAngle { .. } => {
let (e, bulge) = arc_for(sub, a, DVec2::new(value.to_radians(), 0.0), tangent)?;
return Some(self.push_arc(e, bulge));
}
sub @ Sub::ArcCenterLength { .. } => {
let (e, bulge) = arc_for(sub, a, DVec2::new(value, 0.0), tangent)?;
return Some(self.push_arc(e, bulge));
}
_ => return None,
};
self.arc_sub = next;
Some(CmdResult::NeedPoint)
}
/// VPCLIP arc option answered with a point.
fn arc_point(&mut self, pt: DVec3) -> CmdResult {
let Some(a) = self.last2() else {
return CmdResult::NeedPoint;
};
let p = DVec2::new(pt.x, pt.y);
let sub = self.arc_sub;
if sub.is_scalar() {
let value = if sub.is_angle() { (p - a).y.atan2((p - a).x).to_degrees() } else { (p - a).length() };
return self.arc_value(value).unwrap_or(CmdResult::NeedPoint);
}
match sub {
Sub::ArcCenter if (p - a).length_squared() > 1e-12 => self.arc_sub = Sub::ArcCenterEnd { c: p },
Sub::ArcDirection => {
if let Some(dir) = (p - a).try_normalize() {
self.arc_sub = Sub::ArcDirectionEnd { dir };
}
}
Sub::ArcSecond if (p - a).length_squared() > 1e-12 => self.arc_sub = Sub::ArcSecondEnd { s: p },
_ => {
if let Some((e, bulge)) = arc_for(sub, a, p, self.polygon_last_tangent) {
return self.push_arc(e, bulge);
}
}
}
CmdResult::NeedPoint
}
/// VPCLIP CLose in arc mode: a tangent arc back to the start.
fn close_with_arc(&mut self) -> CmdResult {
let (Some(last), Some(first)) = (self.polygon.last().copied(), self.polygon.first().copied()) else {
return CmdResult::NeedPoint;
};
let tangent = self.polygon_last_tangent.map_or(DVec2::X, |t| t.as_dvec2());
let index = self.polygon.len() - 1;
self.polygon_bulges[index] = compute_bulge(DVec2::new(last.x, last.y), tangent, DVec2::new(first.x, first.y));
self.finish_polygon()
}
fn arc_prompt(&self) -> Option<&'static str> {
Some(match self.arc_sub {
Sub::ArcAngle | Sub::ArcCenterAngle { .. } | Sub::ArcRadiusAngle { .. } => "Specify included angle:",
Sub::ArcAngleEnd { .. } => "Specify endpoint of arc (hold Ctrl to switch direction) or [CEnter/Radius]:",
Sub::ArcCenter | Sub::ArcAngleCenter { .. } => "Specify center point of arc:",
Sub::ArcCenterEnd { .. } => "Specify endpoint of arc (hold Ctrl to switch direction) or [Angle/Length]:",
Sub::ArcCenterLength { .. } => "Specify length of chord:",
Sub::ArcDirection => "Specify the tangent direction for the start point of arc:",
Sub::ArcDirectionEnd { .. } => "Specify endpoint of the arc (hold Ctrl to switch direction):",
Sub::ArcRadius | Sub::ArcAngleRadius { .. } => "Specify radius of arc:",
Sub::ArcRadiusEnd { .. } => "Specify endpoint of arc (hold Ctrl to switch direction) or [Angle]:",
Sub::ArcAngleRadiusDir { .. } | Sub::ArcRadiusAngleDir { .. } => "Specify direction of chord for arc:",
Sub::ArcSecond => "Specify second point on arc:",
Sub::ArcSecondEnd { .. } => "Specify end point of arc:",
_ => return None,
})
}
fn clip_text(&mut self, upper: &str) -> Option<CmdResult> {
if self.step == Step::Polygon && self.arc_sub != Sub::None {
let next = match (self.arc_sub, upper) {
(Sub::ArcAngleEnd { angle }, "CE" | "CENTER" | "CENTRE") => Sub::ArcAngleCenter { angle },
(Sub::ArcAngleEnd { angle }, "R" | "RADIUS") => Sub::ArcAngleRadius { angle },
(Sub::ArcCenterEnd { c }, "A" | "ANGLE") => Sub::ArcCenterAngle { c },
(Sub::ArcCenterEnd { c }, "L" | "LENGTH") => Sub::ArcCenterLength { c },
(Sub::ArcRadiusEnd { r }, "A" | "ANGLE") => Sub::ArcRadiusAngle { r },
(sub, text) if sub.is_scalar() => {
let value = if sub.is_angle() {
crate::entities::common::parse_typed_angle(text).map(f64::to_degrees)
} else {
crate::entities::common::parse_typed_length(text)
};
return value.and_then(|v| self.arc_value(v));
}
_ => return None,
};
self.arc_sub = next;
return Some(CmdResult::NeedPoint);
}
match self.step {
Step::ClipChoice => match upper {
"" | "P" | "POLYGONAL" => {
@@ -146,7 +273,18 @@ impl MviewCommand {
Some(self.on_point(last + DVec3::new(direction.x, direction.y, 0.0) * length))
}
Step::Polygon if self.polygon_mode == PolygonMode::Arc => match upper {
"CL" | "CLOSE" if self.polygon.len() >= 2 => Some(self.finish_polygon()),
"CL" | "CLOSE" if self.polygon.len() >= 2 => Some(self.close_with_arc()),
"A" | "ANGLE" | "CE" | "CENTER" | "CENTRE" | "D" | "DIRECTION" | "R" | "RADIUS" | "S"
| "SECOND" | "SECOND PT" => {
self.arc_sub = match upper {
"A" | "ANGLE" => Sub::ArcAngle,
"CE" | "CENTER" | "CENTRE" => Sub::ArcCenter,
"D" | "DIRECTION" => Sub::ArcDirection,
"R" | "RADIUS" => Sub::ArcRadius,
_ => Sub::ArcSecond,
};
Some(CmdResult::NeedPoint)
}
"L" | "LINE" => {
self.polygon_mode = PolygonMode::Line;
Some(CmdResult::NeedPoint)
@@ -367,6 +505,7 @@ impl CadCommand for MviewCommand {
Step::ClipChoice => "Select clipping object or [Polygonal] <Polygonal>:",
Step::ClipLength => "Specify length of line:",
Step::Polygon if self.polygon.is_empty() => "Specify start point:",
Step::Polygon if self.arc_prompt().is_some() => self.arc_prompt().unwrap_or_default(),
Step::Polygon if self.polygon_mode == PolygonMode::Arc => {
"Enter an arc boundary option\n[Angle/CEnter/CLose/Direction/Line/Radius/Second pt/Undo/Endpoint of arc] <Endpoint>:"
}
@@ -418,9 +557,20 @@ impl CadCommand for MviewCommand {
if self.clip.is_some() {
return match self.step {
Step::ClipChoice => vec![CmdOption::new("Polygonal", "P")],
Step::Polygon if self.arc_sub != Sub::None => match self.arc_sub {
Sub::ArcAngleEnd { .. } => vec![CmdOption::new("CEnter", "CE"), CmdOption::new("Radius", "R")],
Sub::ArcCenterEnd { .. } => vec![CmdOption::new("Angle", "A"), CmdOption::new("Length", "L")],
Sub::ArcRadiusEnd { .. } => vec![CmdOption::new("Angle", "A")],
_ => Vec::new(),
},
Step::Polygon if self.polygon_mode == PolygonMode::Arc => vec![
CmdOption::new("Angle", "A"),
CmdOption::new("CEnter", "CE"),
CmdOption::new("CLose", "CL"),
CmdOption::new("Direction", "D"),
CmdOption::new("Line", "L"),
CmdOption::new("Radius", "R"),
CmdOption::new("Second pt", "S"),
CmdOption::new("Undo", "U"),
],
Step::Polygon if !self.polygon.is_empty() => {
@@ -479,6 +629,7 @@ impl CadCommand for MviewCommand {
},
None => CmdResult::NeedPoint,
},
Step::Polygon if self.arc_sub != Sub::None => self.arc_point(pt),
Step::Polygon => {
if let Some(last) = self.polygon.last().copied() {
let last_index = self.polygon.len() - 1;
@@ -620,7 +771,7 @@ impl CadCommand for MviewCommand {
}
fn input_kind(&self) -> InputKind {
if self.step == Step::ClipLength {
if self.step == Step::ClipLength || (self.step == Step::Polygon && self.arc_sub.is_scalar()) {
InputKind::FreeText
} else if self.step == Step::ChooseView {
InputKind::FreeText
@@ -707,6 +858,10 @@ impl CadCommand for MviewCommand {
self.step = Step::Polygon;
return Some(CmdResult::NeedPoint);
}
if self.arc_sub != Sub::None {
self.arc_sub = Sub::None;
return Some(CmdResult::NeedPoint);
}
if self.step == Step::Polygon && !self.polygon.is_empty() {
Some(self.undo_polygon())
} else {
+13
View File
@@ -121,6 +121,19 @@ impl Scene {
handles: &[Handle],
) -> Vec<Handle> {
let mut expanded = self.expanded_with_leaders(handles);
// A viewport's clip boundary goes with its viewport: picking the
// boundary selects both, erasing it erases the viewport.
let viewports: Vec<Handle> = expanded
.iter()
.filter_map(|h| self.document.get_entity(*h).map(|e| (*h, e)))
.flat_map(|(h, e)| {
e.common().reactors.iter().copied().filter(move |r| {
matches!(self.document.get_entity(*r),
Some(EntityType::Viewport(vp)) if vp.clip_boundary_handle == h)
})
})
.collect();
expanded.extend(viewports);
expanded.sort_unstable_by_key(|handle| handle.value());
expanded.dedup();
expanded