CLIP on raster images and viewports, IMAGECLIP, VPCLIP, clip depth rules

- CLIP continues as IMAGECLIP on a raster image and as VPCLIP on a layout
  viewport, with the reference prompts and stored data.
- Image clip boundaries sit on pixel centres, OFF only clears the display
  flag, and an inverted boundary cuts the image.
- VPCLIP polygon (Arc/Length/Close/Undo), clipping object and Delete; the
  viewport shrinks to the boundary's exact extents, view kept.
- XCLIP Clipdepth: Remove clears only the plane asked, a front plane behind
  the back one is rejected with the reference message.
- Attached and imported images carry the reference display flags.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-25 10:33:49 +03:00
1 parent 857f2f2e58
commit bd17e7e0d4
12 files changed
+569 -112

No files matched your search

+1
View File
@@ -96,6 +96,7 @@ impl ImageCommand {
world_height,
);
img.flags = codec::entities::ImageDisplayFlags::SHOW_IMAGE
| codec::entities::ImageDisplayFlags::SHOW_NOT_ALIGNED
| codec::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY;
self.plane.place_entity(EntityType::RasterImage(img))
}
+129 -17
View File
@@ -1,17 +1,23 @@
// PDFCLIP — clip a PDF underlay to a boundary (CLIP continues here for a
// PDF underlay).
// PDFCLIP / IMAGECLIP — clip a PDF underlay or a raster image to a boundary
// (CLIP continues here for either).
//
// Select PDF to clip:
// Select PDF to clip: (Select image to clip:)
// Enter PDF clipping option [ON/OFF/Delete/New boundary] <New boundary>:
// Delete old boundary? [Yes/No] <Yes>: (when one exists)
// (Enter image clipping option [ON/OFF/Delete/New boundary] <New>:)
// Delete old boundary? [Yes/No] <Yes>: (when one exists; [No/Yes] for
// an image)
// Outside mode - Objects outside boundary will be hidden.
// Specify clipping boundary or select invert option:
// [Select polyline/Polygonal/Rectangular/Invert clip] <Rectangular>:
//
// The boundary is stored in underlay units; the clip-inside bit follows the
// Invert choice.
// The boundary is stored in underlay units (image: raster pixels, a
// rectangle as its two corners); the clip-inside bit follows the Invert
// choice.
use codec::entities::{Underlay, UnderlayDisplayFlags, UnderlayType};
use codec::entities::{
ClipBoundary, ClipMode, ImageDisplayFlags, RasterImage, Underlay, UnderlayDisplayFlags,
UnderlayType,
};
use codec::types::{Handle, Vector2};
use codec::EntityType;
use glam::DVec3;
@@ -35,6 +41,9 @@ pub struct PdfClipCommand {
step: Step,
handle: Handle,
underlay: Option<Underlay>,
/// IMAGECLIP: the image being clipped (`underlay` stays empty).
image: Option<RasterImage>,
image_mode: bool,
picked: Option<EntityType>,
inverted: bool,
points: Vec<DVec3>,
@@ -50,12 +59,90 @@ impl PdfClipCommand {
step: Step::Select,
handle: Handle::NULL,
underlay: None,
image: None,
image_mode: false,
picked: None,
inverted: false,
points: Vec::new(),
}
}
/// IMAGECLIP: asks for an image.
pub fn image() -> Self {
Self { image_mode: true, ..Self::new() }
}
/// An image CLIP picked: straight to the clipping option.
pub fn for_image(handle: Handle, image: RasterImage) -> Self {
let mut command = Self::image();
command.handle = handle;
command.image = Some(image);
command.step = Step::Option;
command
}
fn finish_image(&self, image: RasterImage) -> CmdResult {
CmdResult::UpdateEntityAndFinish {
handle: self.handle,
entity: EntityType::RasterImage(image),
}
}
/// Image options: ON / OFF switch only the display flag (the boundary
/// stays), Delete drops the boundary back to the whole image, New asks
/// to replace a boundary that exists.
fn image_option(&mut self, token: &str) -> CmdResult {
let Some(mut image) = self.image.clone() else {
return CmdResult::Cancel;
};
match token {
"ON" => image.flags |= ImageDisplayFlags::USE_CLIPPING_BOUNDARY,
"OFF" => image.flags -= ImageDisplayFlags::USE_CLIPPING_BOUNDARY,
"D" | "DELETE" => {
let mode = image.clip_boundary.clip_mode;
image.clip_boundary = ClipBoundary::full_image(image.size.x, image.size.y);
image.clip_boundary.clip_mode = mode;
image.clipping_enabled = false;
}
"" | "N" | "NEW" | "NEW BOUNDARY" if image.clipping_enabled => {
self.step = Step::DeleteOld;
return CmdResult::NeedPoint;
}
"" | "N" | "NEW" | "NEW BOUNDARY" => return self.enter_mode(),
_ => return CmdResult::ReportError("Invalid option keyword.".to_string()),
}
self.finish_image(image)
}
/// A rectangle keeps its two corners (lowest and highest raster
/// coordinates); a polygon is closed by repeating its first vertex.
fn apply_image_boundary(&self, world: &[DVec3], rectangle: bool) -> CmdResult {
let Some(mut image) = self.image.clone() else {
return CmdResult::Cancel;
};
let raster: Vec<Vector2> = world
.iter()
.map(|p| crate::entities::raster_image::image_world_to_clip(&image, p.to_array()))
.collect();
let mut boundary = if rectangle {
let (mut lo, mut hi) = (raster[0], raster[0]);
for p in &raster {
lo = Vector2::new(lo.x.min(p.x), lo.y.min(p.y));
hi = Vector2::new(hi.x.max(p.x), hi.y.max(p.y));
}
ClipBoundary::rectangular(lo, hi)
} else {
let mut ring = raster;
ring.push(ring[0]);
ClipBoundary::polygonal(ring)
};
boundary.clip_mode = if self.inverted { ClipMode::Inside } else { ClipMode::Outside };
image.clip_boundary = boundary;
image.clipping_enabled = true;
image.flags |= ImageDisplayFlags::USE_CLIPPING_BOUNDARY;
self.finish_image(image)
}
/// A new boundary for an underlay already chosen (the underlay tab's
/// Create Clipping Boundary): the New boundary branch at once. Returns the
/// command and the result of its first step.
@@ -99,7 +186,10 @@ impl PdfClipCommand {
Vector2::new((dx * c + dy * s) / sx, (-dx * s + dy * c) / sy)
}
fn apply_boundary(&self, world: &[DVec3]) -> CmdResult {
fn apply_boundary(&self, world: &[DVec3], rectangle: bool) -> CmdResult {
if self.image.is_some() {
return self.apply_image_boundary(world, rectangle);
}
let Some(mut underlay) = self.underlay.clone() else {
return CmdResult::Cancel;
};
@@ -110,6 +200,9 @@ impl PdfClipCommand {
}
fn option(&mut self, text: &str) -> CmdResult {
if self.image.is_some() {
return self.image_option(&text.trim().to_ascii_uppercase());
}
let Some(mut underlay) = self.underlay.clone() else {
return CmdResult::Cancel;
};
@@ -174,15 +267,20 @@ impl PdfClipCommand {
impl CadCommand for PdfClipCommand {
fn name(&self) -> &'static str {
"PDFCLIP"
if self.image_mode { "IMAGECLIP" } else { "PDFCLIP" }
}
fn prompt(&self) -> String {
match self.step {
Step::Select if self.image_mode => "Select image to clip:".to_string(),
Step::Select => "Select PDF to clip:".to_string(),
Step::Option if self.image_mode => {
"Enter image clipping option [ON/OFF/Delete/New boundary] <New>:".to_string()
}
Step::Option => {
"Enter PDF clipping option [ON/OFF/Delete/New boundary] <New boundary>:".to_string()
}
Step::DeleteOld if self.image_mode => "Delete old boundary? [No/Yes] <Yes>:".to_string(),
Step::DeleteOld => "Delete old boundary? [Yes/No] <Yes>:".to_string(),
Step::Mode => {
"[Select polyline/Polygonal/Rectangular/Invert clip] <Rectangular>:".to_string()
@@ -206,6 +304,9 @@ impl CadCommand for PdfClipCommand {
CmdOption::new("Delete", "D"),
CmdOption::new("New boundary", "N"),
],
Step::DeleteOld if self.image_mode => {
vec![CmdOption::new("No", "N"), CmdOption::new("Yes", "Y")]
}
Step::DeleteOld => vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")],
Step::Mode => vec![
CmdOption::new("Select polyline", "S"),
@@ -247,6 +348,16 @@ impl CadCommand for PdfClipCommand {
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
let picked = self.picked.take();
match self.step {
Step::Select if self.image_mode => match picked {
Some(EntityType::RasterImage(image)) => {
self.handle = handle;
self.image = Some(image);
self.step = Step::Option;
CmdResult::NeedPoint
}
// Anything else: the prompt again, as the reference does.
_ => CmdResult::NeedPoint,
},
Step::Select => match picked {
Some(EntityType::Underlay(u)) if u.underlay_type == UnderlayType::Pdf => {
self.handle = handle;
@@ -264,7 +375,7 @@ impl CadCommand for PdfClipCommand {
.iter()
.map(|v| DVec3::new(v.location.x, v.location.y, z))
.collect();
self.apply_boundary(&world)
self.apply_boundary(&world, false)
}
_ => CmdResult::ReportError("Invalid object selected.".to_string()),
},
@@ -281,7 +392,7 @@ impl CadCommand for PdfClipCommand {
}
Step::RectSecond => {
let first = self.points[0];
self.apply_boundary(&Self::rectangle(first, pt))
self.apply_boundary(&Self::rectangle(first, pt), true)
}
Step::PolyPoints => {
self.points.push(pt);
@@ -296,9 +407,10 @@ impl CadCommand for PdfClipCommand {
Step::Option => self.option(text),
Step::DeleteOld => match text.trim().to_ascii_uppercase().as_str() {
"" | "Y" | "YES" => self.enter_mode(),
"N" | "NO" => match self.underlay.clone() {
Some(underlay) => self.finish(underlay),
None => CmdResult::Cancel,
"N" | "NO" => match (self.image.clone(), self.underlay.clone()) {
(Some(image), _) => self.finish_image(image),
(None, Some(underlay)) => self.finish(underlay),
_ => CmdResult::Cancel,
},
_ => CmdResult::ReportError("Invalid option keyword.".to_string()),
},
@@ -310,7 +422,7 @@ impl CadCommand for PdfClipCommand {
}
"C" | "CLOSE" if self.points.len() >= 3 => {
let points = self.points.clone();
self.apply_boundary(&points)
self.apply_boundary(&points, false)
}
_ => return None,
},
@@ -325,7 +437,7 @@ impl CadCommand for PdfClipCommand {
Step::Mode => self.mode(""),
Step::PolyPoints if self.points.len() >= 3 => {
let points = self.points.clone();
self.apply_boundary(&points)
self.apply_boundary(&points, false)
}
Step::PolyPoints => CmdResult::NeedPoint,
_ => CmdResult::Cancel,
@@ -349,5 +461,5 @@ impl CadCommand for PdfClipCommand {
}
inventory::submit!(crate::command::CommandRegistration {
names: &["PDFCLIP"]
names: &["PDFCLIP", "IMAGECLIP"]
});
+39 -5
View File
@@ -13,7 +13,8 @@
// Specify front clip point or [Distance/Remove]:
//
// CLIP asks for one object: a block reference continues as XCLIP, a PDF
// underlay as PDFCLIP, anything else is "*Invalid selection*".
// underlay as PDFCLIP, a raster image as IMAGECLIP, a layout viewport as
// VPCLIP, anything else is "*Invalid selection*".
use codec::entities::UnderlayType;
use codec::types::Handle;
@@ -22,6 +23,7 @@ use glam::DVec3;
use crate::command::{CadCommand, CmdOption, CmdResult, InputKind};
use crate::modules::insert::pdf_clip::PdfClipCommand;
use crate::modules::layout::mview::MviewCommand;
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -43,8 +45,9 @@ pub enum XclipAction {
Polyline,
/// Boundary in WCS (two points = rectangle corners).
New { boundary: Vec<[f64; 2]>, inverted: bool },
/// Front and back clipping planes; `None` removes one.
Depth { front: Option<f64>, back: Option<f64> },
/// Front and back clipping planes: `Some(None)` removes one, `None`
/// leaves it. `rejected` prints that the front plane was not applied.
Depth { front: Option<Option<f64>>, back: Option<Option<f64>>, rejected: bool },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -76,9 +79,12 @@ pub struct XclipCommand {
inverted: bool,
points: Vec<DVec3>,
picked: Option<EntityType>,
/// The front plane answered: a distance, or `None` for Remove.
front: Option<f64>,
}
pub const DEPTH_REJECTED: &str = "Error: Front clipping plane behind back clipping plane. Clipdepth not applied.";
impl XclipCommand {
pub fn new() -> Self {
Self {
@@ -178,7 +184,19 @@ impl XclipCommand {
fn depth(&mut self, text: &str) -> CmdResult {
match (self.step, text.trim().to_ascii_uppercase().as_str()) {
(_, "R" | "REMOVE") => self.act(XclipAction::Depth { front: None, back: None }),
// Remove clears only the plane being asked; the back one is still asked.
(Step::Front, "R" | "REMOVE") => {
self.front = None;
self.step = Step::Back;
CmdResult::NeedPoint
}
// Back Remove after a front distance: the back plane goes, the
// front is not applied.
(Step::Back, "R" | "REMOVE") => self.act(XclipAction::Depth {
front: self.front.is_none().then_some(None),
back: Some(None),
rejected: self.front.is_some(),
}),
(Step::Front, "D" | "DISTANCE") => {
self.step = Step::FrontDistance;
CmdResult::NeedPoint
@@ -202,7 +220,13 @@ impl XclipCommand {
self.step = Step::Back;
CmdResult::NeedPoint
}
_ => self.act(XclipAction::Depth { front: self.front, back: Some(d) }),
// A front plane behind the back one changes nothing.
_ if self.front.is_some_and(|front| front < d) => self.act(XclipAction::Depth {
front: None,
back: None,
rejected: true,
}),
_ => self.act(XclipAction::Depth { front: Some(self.front), back: Some(Some(d)), rejected: false }),
}
}
@@ -458,6 +482,16 @@ impl CadCommand for ClipCommand {
self.inner = Some(Box::new(PdfClipCommand::for_underlay(handle, u)));
CmdResult::NeedPoint
}
Some(EntityType::RasterImage(image)) => {
self.inner = Some(Box::new(PdfClipCommand::for_image(handle, image)));
CmdResult::NeedPoint
}
// The layout's own paper-space viewport (id 1) is not clippable.
Some(EntityType::Viewport(vp)) if vp.id != 1 => {
let clipped = !vp.clip_boundary_handle.is_null();
self.inner = Some(Box::new(MviewCommand::vpclip(handle, clipped)));
CmdResult::NeedPoint
}
_ => CmdResult::ReportError("*Invalid selection*".to_string()),
}
}
+171 -5
View File
@@ -1,4 +1,16 @@
// MVIEW — interactive paper-space viewport creation.
//
// VPCLIP (and CLIP on a layout viewport) reuses the polygon and object
// steps to clip an existing viewport:
// Select viewport to clip:
// Select clipping object or [Polygonal] <Polygonal>: (Delete also taken
// when clipped)
// Specify start point:
// Specify next point or [Arc/Length/Undo]:
// Specify next point or [Arc/Close/Length/Undo]:
// Enter an arc boundary option
// [Angle/CEnter/CLose/Direction/Line/Radius/Second pt/Undo/Endpoint of arc] <Endpoint>:
// Specify length of line:
use codec::entities::{LwPolyline, LwVertex, Viewport};
use codec::tables::View;
@@ -37,6 +49,12 @@ enum Step {
DefineNewFirst,
DefineNewSecond,
PlaceView,
/// VPCLIP: the viewport to clip.
ClipSelect,
/// VPCLIP: a clipping object, or Polygonal / Delete.
ClipChoice,
/// VPCLIP Length: a line of that length along the last direction.
ClipLength,
}
#[derive(Clone, Copy, PartialEq)]
@@ -56,6 +74,10 @@ pub struct MviewCommand {
views: Vec<View>,
paper_bounds: ((f64, f64), (f64, f64)),
original_layout: String,
/// VPCLIP: the viewport being clipped (NULL until chosen) and whether it
/// already has a boundary.
clip: Option<(Handle, bool)>,
picked: Option<EntityType>,
}
impl MviewCommand {
@@ -75,6 +97,77 @@ impl MviewCommand {
views,
paper_bounds,
original_layout,
clip: None,
picked: None,
}
}
/// VPCLIP asking for the viewport.
pub fn vpclip_select() -> Self {
let mut command = Self::new(String::new(), ((0.0, 0.0), (0.0, 0.0)), Vec::new());
command.clip = Some((Handle::NULL, false));
command.step = Step::ClipSelect;
command
}
/// VPCLIP on a chosen viewport.
pub fn vpclip(viewport: Handle, clipped: bool) -> Self {
let mut command = Self::vpclip_select();
command.clip = Some((viewport, clipped));
command.step = Step::ClipChoice;
command
}
fn clip_target(&self) -> Handle {
self.clip.map_or(Handle::NULL, |(handle, _)| handle)
}
fn clip_text(&mut self, upper: &str) -> Option<CmdResult> {
match self.step {
Step::ClipChoice => match upper {
"" | "P" | "POLYGONAL" => {
self.step = Step::Polygon;
Some(CmdResult::NeedPoint)
}
"D" | "DELETE" if self.clip.is_some_and(|(_, clipped)| clipped) => {
Some(CmdResult::MviewCreateClipped {
boundary: None,
boundary_handle: Handle::NULL,
target: self.clip_target(),
})
}
_ => None,
},
Step::ClipLength => {
let length = upper.parse::<f64>().ok()?;
let last = *self.polygon.last()?;
let direction = self.polygon_last_tangent.map_or(DVec2::X, |t| t.as_dvec2());
self.step = Step::Polygon;
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()),
"L" | "LINE" => {
self.polygon_mode = PolygonMode::Line;
Some(CmdResult::NeedPoint)
}
"U" | "UNDO" if !self.polygon.is_empty() => Some(self.undo_polygon()),
_ => None,
},
Step::Polygon => match upper {
"A" | "ARC" if !self.polygon.is_empty() => {
self.polygon_mode = PolygonMode::Arc;
Some(CmdResult::NeedPoint)
}
"C" | "CLOSE" if self.polygon.len() >= 3 => Some(self.finish_polygon()),
"L" | "LENGTH" if !self.polygon.is_empty() => {
self.step = Step::ClipLength;
Some(CmdResult::NeedPoint)
}
"U" | "UNDO" if !self.polygon.is_empty() => Some(self.undo_polygon()),
_ => None,
},
_ => None,
}
}
@@ -158,6 +251,7 @@ impl MviewCommand {
Some(boundary) => CmdResult::MviewCreateClipped {
boundary: Some(boundary),
boundary_handle: Handle::NULL,
target: self.clip_target(),
},
None => CmdResult::Cancel,
}
@@ -263,10 +357,24 @@ impl MviewCommand {
impl CadCommand for MviewCommand {
fn name(&self) -> &'static str {
"MVIEW"
if self.clip.is_some() { "VPCLIP" } else { "MVIEW" }
}
fn prompt(&self) -> String {
if self.clip.is_some() {
return match self.step {
Step::ClipSelect => "Select viewport to clip:",
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.polygon_mode == PolygonMode::Arc => {
"Enter an arc boundary option\n[Angle/CEnter/CLose/Direction/Line/Radius/Second pt/Undo/Endpoint of arc] <Endpoint>:"
}
Step::Polygon if self.polygon.len() < 3 => "Specify next point or [Arc/Length/Undo]:",
_ => "Specify next point or [Arc/Close/Length/Undo]:",
}
.to_string();
}
match self.step {
Step::RectangleFirst => t!(
"MVIEW Specify corner of viewport or [Polygonal/Object/Fit/Insert view]:"
@@ -302,10 +410,31 @@ impl CadCommand for MviewCommand {
t!("MVIEW New view Specify opposite model-space corner:").into_owned()
}
Step::PlaceView => t!("MVIEW Insert view Specify placement point:").into_owned(),
Step::ClipSelect | Step::ClipChoice | Step::ClipLength => String::new(),
}
}
fn options(&self) -> Vec<CmdOption> {
if self.clip.is_some() {
return match self.step {
Step::ClipChoice => vec![CmdOption::new("Polygonal", "P")],
Step::Polygon if self.polygon_mode == PolygonMode::Arc => vec![
CmdOption::new("CLose", "CL"),
CmdOption::new("Line", "L"),
CmdOption::new("Undo", "U"),
],
Step::Polygon if !self.polygon.is_empty() => {
let mut options = vec![CmdOption::new("Arc", "A")];
if self.polygon.len() >= 3 {
options.push(CmdOption::new("Close", "C"));
}
options.push(CmdOption::new("Length", "L"));
options.push(CmdOption::new("Undo", "U"));
options
}
_ => Vec::new(),
};
}
match self.step {
Step::RectangleFirst => vec![
CmdOption::new(t!("Polygonal").as_ref(), "POLYGONAL"),
@@ -427,12 +556,19 @@ impl CadCommand for MviewCommand {
},
None => CmdResult::Cancel,
},
Step::Object | Step::ChooseView => CmdResult::NeedPoint,
Step::Object | Step::ChooseView | Step::ClipSelect | Step::ClipChoice => {
CmdResult::NeedPoint
}
Step::ClipLength => CmdResult::NeedPoint,
}
}
fn on_enter(&mut self) -> CmdResult {
match self.step {
Step::ClipChoice => {
self.step = Step::Polygon;
CmdResult::NeedPoint
}
Step::Polygon if self.polygon.len() >= 3 => self.finish_polygon(),
Step::DefineNewFirst | Step::DefineNewSecond => {
CmdResult::MviewCancelToLayout(self.original_layout.clone())
@@ -451,23 +587,45 @@ impl CadCommand for MviewCommand {
}
fn needs_entity_pick(&self) -> bool {
self.step == Step::Object
matches!(self.step, Step::Object | Step::ClipSelect | Step::ClipChoice)
}
fn inject_before_entity_pick(&self) -> bool {
self.clip.is_some()
}
fn inject_picked_entity(&mut self, entity: EntityType) {
self.picked = Some(entity);
}
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
if handle.is_null() {
return CmdResult::NeedPoint;
}
if self.step == Step::ClipSelect {
return match self.picked.take() {
Some(EntityType::Viewport(vp)) if vp.id != 1 => {
self.clip = Some((handle, !vp.clip_boundary_handle.is_null()));
self.step = Step::ClipChoice;
CmdResult::NeedPoint
}
_ => CmdResult::ReportError("Object selected was not a viewport\n.".to_string()),
};
}
CmdResult::MviewCreateClipped {
boundary: None,
boundary_handle: handle,
target: self.clip_target(),
}
}
fn input_kind(&self) -> InputKind {
if self.step == Step::ChooseView {
if self.step == Step::ClipLength {
InputKind::FreeText
} else if self.step == Step::ChooseView {
InputKind::FreeText
} else if self.step == Step::RectangleFirst
|| self.step == Step::ClipChoice
|| (self.step == Step::Polygon && !self.polygon.is_empty())
{
InputKind::SingleToken
@@ -478,6 +636,7 @@ impl CadCommand for MviewCommand {
fn point_step_accepts_keywords(&self) -> bool {
self.step == Step::RectangleFirst
|| self.step == Step::ClipChoice
|| (self.step == Step::Polygon && !self.polygon.is_empty())
}
@@ -492,6 +651,9 @@ impl CadCommand for MviewCommand {
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let keyword = text.trim();
let upper = keyword.to_ascii_uppercase();
if self.clip.is_some() {
return self.clip_text(&upper);
}
match self.step {
Step::RectangleFirst => match upper.as_str() {
"P" | "POLYGONAL" => {
@@ -541,6 +703,10 @@ impl CadCommand for MviewCommand {
}
fn on_undo_step(&mut self) -> Option<CmdResult> {
if self.clip.is_some() && self.step == Step::ClipLength {
self.step = Step::Polygon;
return Some(CmdResult::NeedPoint);
}
if self.step == Step::Polygon && !self.polygon.is_empty() {
Some(self.undo_polygon())
} else {
@@ -580,4 +746,4 @@ impl CadCommand for MviewCommand {
// ── Autocomplete registry ─────────────────────────────────
inventory::submit!(crate::command::CommandRegistration { names: &["MVIEW"] }); // MviewCommand
inventory::submit!(crate::command::CommandRegistration { names: &["MVIEW", "VPCLIP"] }); // MviewCommand