forked from 1b-agent/opencadstudio-upstream
feat(frame): complete frame controls
Preserve frame geometry for selection while separating viewport and plot visibility across supported reference entities. Refs #642
This commit is contained in:
commit
44efb8fd53
88 files changed
+1538
-702
No files matched your search
@@ -223,6 +223,8 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -279,6 +281,8 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -164,6 +164,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -195,6 +195,8 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -183,6 +183,8 @@ impl CadCommand for DimBaselineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -163,6 +163,8 @@ impl CadCommand for DimContinueCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -89,6 +89,8 @@ impl CadCommand for DimJogLineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -106,6 +106,8 @@ impl CadCommand for DimTeditCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -303,6 +303,8 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -240,6 +240,8 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -230,6 +230,8 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -465,6 +465,8 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -140,6 +140,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -96,6 +96,8 @@ impl CadCommand for QdimCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -200,6 +200,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -202,6 +202,8 @@ impl CadCommand for TableCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -106,6 +106,8 @@ impl CadCommand for ToleranceCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -204,6 +204,8 @@ impl CadCommand for AttdefCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -348,6 +348,8 @@ impl HatchCommand {
|
||||
scale: source.scale,
|
||||
world_origin: origin,
|
||||
boundary_wcs: Some(std::sync::Arc::new(wcs)),
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
draw_depth: source.draw_depth,
|
||||
@@ -387,6 +389,8 @@ impl HatchCommand {
|
||||
scale: 1.0,
|
||||
world_origin: origin,
|
||||
boundary_wcs: Some(std::sync::Arc::new(wcs)),
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
draw_depth: 0.0,
|
||||
@@ -699,6 +703,8 @@ impl GradientCommand {
|
||||
scale: 1.0,
|
||||
world_origin: origin,
|
||||
boundary_wcs: Some(std::sync::Arc::new(wcs)),
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
draw_depth: 0.0,
|
||||
|
||||
@@ -184,6 +184,8 @@ impl CadCommand for MlineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -126,6 +126,8 @@ impl CadCommand for ImageCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -88,6 +88,8 @@ impl CadCommand for RayCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -188,6 +190,8 @@ impl CadCommand for XLineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
+188
-243
@@ -2,7 +2,9 @@
|
||||
|
||||
use acadrust::entities::{Wipeout, WipeoutClipType};
|
||||
use acadrust::types::{Vector2, Vector3};
|
||||
use acadrust::{EntityType, Handle};
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
use cadkernel::geom2d::{polygon_frame, Tolerance};
|
||||
use cadkernel::space::Plane;
|
||||
use glam::DVec3;
|
||||
use crate::t;
|
||||
|
||||
@@ -26,6 +28,8 @@ pub struct WipeoutCommand {
|
||||
first: Option<DVec3>,
|
||||
points: Vec<DVec3>,
|
||||
plane: WorkingPlane,
|
||||
selected_polyline: Option<Handle>,
|
||||
frame_mode: i16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
@@ -33,15 +37,19 @@ enum WipeoutMode {
|
||||
Draw,
|
||||
Polyline,
|
||||
Rectangular,
|
||||
Frames,
|
||||
ErasePolyline,
|
||||
}
|
||||
|
||||
impl WipeoutCommand {
|
||||
pub fn new_polygonal() -> Self {
|
||||
pub fn new_polygonal(frame_mode: i16) -> Self {
|
||||
Self {
|
||||
mode: WipeoutMode::Draw,
|
||||
first: None,
|
||||
points: Vec::new(),
|
||||
plane: WorkingPlane::default(),
|
||||
selected_polyline: None,
|
||||
frame_mode: frame_mode.clamp(0, 2),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +59,8 @@ impl WipeoutCommand {
|
||||
first: None,
|
||||
points: Vec::new(),
|
||||
plane: WorkingPlane::default(),
|
||||
selected_polyline: None,
|
||||
frame_mode: 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,17 +71,27 @@ impl WipeoutCommand {
|
||||
first: None,
|
||||
points: Vec::new(),
|
||||
plane: WorkingPlane::default(),
|
||||
selected_polyline: None,
|
||||
frame_mode: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_draw(&self) -> CmdResult {
|
||||
let local: Vec<DVec3> = self
|
||||
let local: Vec<[f64; 2]> = self
|
||||
.points
|
||||
.iter()
|
||||
.map(|point| self.plane.to_local(*point))
|
||||
.map(|point| {
|
||||
let point = self.plane.to_local(*point);
|
||||
[point.x, point.y]
|
||||
})
|
||||
.collect();
|
||||
match make_poly_wipeout(&local) {
|
||||
Some(entity) => CmdResult::CommitAndExit(self.plane.place_entity(entity)),
|
||||
let plane = Plane::from_axes(
|
||||
self.plane.origin.to_array(),
|
||||
self.plane.x.to_array(),
|
||||
self.plane.y.to_array(),
|
||||
);
|
||||
match make_poly_wipeout(&local, plane) {
|
||||
Some(entity) => CmdResult::CommitAndExit(entity),
|
||||
None => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
@@ -94,7 +114,7 @@ impl CadCommand for WipeoutCommand {
|
||||
fn prompt(&self) -> String {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw if self.points.is_empty() => {
|
||||
t!("WIPEOUT Specify first point or [Polyline]:").into_owned()
|
||||
t!("WIPEOUT Specify first point or [Frames/Polyline] <Polyline>:").into_owned()
|
||||
}
|
||||
WipeoutMode::Draw => {
|
||||
let n = self.points.len();
|
||||
@@ -113,18 +133,38 @@ impl CadCommand for WipeoutCommand {
|
||||
WipeoutMode::Rectangular => {
|
||||
t!("WIPEOUT Rectangular Specify opposite corner:").into_owned()
|
||||
}
|
||||
WipeoutMode::Frames => t!(
|
||||
"WIPEOUT Frames Enter frame mode [Off/On/DisplayButNotPlot] <%{mode}>:",
|
||||
mode = self.frame_mode
|
||||
)
|
||||
.into_owned(),
|
||||
WipeoutMode::ErasePolyline => {
|
||||
t!("WIPEOUT Polyline Erase source polyline? [Yes/No] <No>:").into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw if self.points.is_empty() => {
|
||||
vec![CmdOption::new(t!("Polyline").as_ref(), "P")]
|
||||
vec![
|
||||
CmdOption::new(t!("Frames").as_ref(), "F"),
|
||||
CmdOption::new(t!("Polyline").as_ref(), "P"),
|
||||
]
|
||||
}
|
||||
WipeoutMode::Draw => vec![
|
||||
CmdOption::new(t!("Undo").as_ref(), "U"),
|
||||
CmdOption::new(t!("Close").as_ref(), "C"),
|
||||
],
|
||||
WipeoutMode::Frames => vec![
|
||||
CmdOption::new(t!("Off").as_ref(), "OFF"),
|
||||
CmdOption::new(t!("On").as_ref(), "ON"),
|
||||
CmdOption::new(t!("Display but not plot").as_ref(), "D"),
|
||||
],
|
||||
WipeoutMode::ErasePolyline => vec![
|
||||
CmdOption::new(t!("Yes").as_ref(), "Y"),
|
||||
CmdOption::new(t!("No").as_ref(), "N"),
|
||||
],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -133,9 +173,13 @@ impl CadCommand for WipeoutCommand {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw => {
|
||||
if let Some(first) = self.points.first() {
|
||||
let distance_squared =
|
||||
(point.x - first.x).powi(2) + (point.y - first.y).powi(2);
|
||||
if self.points.len() >= 3 && distance_squared < 1e-12 {
|
||||
let first = self.plane.to_local(*first);
|
||||
let point = self.plane.to_local(point);
|
||||
let distance = cadkernel::geom2d::Vec2::new(point.x, point.y)
|
||||
.distance(cadkernel::geom2d::Vec2::new(first.x, first.y));
|
||||
if self.points.len() >= 3
|
||||
&& distance <= Tolerance::default().linear()
|
||||
{
|
||||
return self.finish_draw();
|
||||
}
|
||||
}
|
||||
@@ -146,21 +190,35 @@ impl CadCommand for WipeoutCommand {
|
||||
if let Some(first) = self.first {
|
||||
let first = self.plane.to_local(first);
|
||||
let point = self.plane.to_local(point);
|
||||
CmdResult::CommitAndExit(
|
||||
self.plane.place_entity(make_rect_wipeout(first, point)),
|
||||
)
|
||||
let plane = Plane::from_axes(
|
||||
self.plane.origin.to_array(),
|
||||
self.plane.x.to_array(),
|
||||
self.plane.y.to_array(),
|
||||
);
|
||||
make_rect_wipeout(first, point, plane)
|
||||
.map_or(CmdResult::NeedPoint, CmdResult::CommitAndExit)
|
||||
} else {
|
||||
self.first = Some(point);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
WipeoutMode::Polyline => CmdResult::NeedPoint,
|
||||
WipeoutMode::Polyline | WipeoutMode::Frames | WipeoutMode::ErasePolyline => {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Polyline;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
WipeoutMode::Draw if self.points.len() >= 3 => self.finish_draw(),
|
||||
WipeoutMode::Frames => {
|
||||
CmdResult::Dispatch(format!("WIPEOUTFRAME {}", self.frame_mode))
|
||||
}
|
||||
WipeoutMode::ErasePolyline => self.finish_polyline(false),
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
@@ -177,33 +235,55 @@ impl CadCommand for WipeoutCommand {
|
||||
if handle.is_null() {
|
||||
CmdResult::NeedPoint
|
||||
} else {
|
||||
CmdResult::WipeoutFromPolyline(handle)
|
||||
self.selected_polyline = Some(handle);
|
||||
self.mode = WipeoutMode::ErasePolyline;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
self.mode == WipeoutMode::Draw
|
||||
matches!(
|
||||
self.mode,
|
||||
WipeoutMode::Draw | WipeoutMode::Frames | WipeoutMode::ErasePolyline
|
||||
)
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
self.mode == WipeoutMode::Draw
|
||||
matches!(
|
||||
self.mode,
|
||||
WipeoutMode::Draw | WipeoutMode::Frames | WipeoutMode::ErasePolyline
|
||||
)
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if self.mode != WipeoutMode::Draw {
|
||||
return None;
|
||||
}
|
||||
match text.trim().to_ascii_uppercase().as_str() {
|
||||
"P" | "POLYLINE" if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Polyline;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"U" | "UNDO" if !self.points.is_empty() => {
|
||||
Some(self.undo_point())
|
||||
}
|
||||
"C" | "CLOSE" if self.points.len() >= 3 => {
|
||||
Some(self.finish_draw())
|
||||
}
|
||||
let text = text.trim().to_ascii_uppercase();
|
||||
match self.mode {
|
||||
WipeoutMode::Draw => match text.as_str() {
|
||||
"F" | "FRAMES" if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Frames;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"P" | "POLYLINE" if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Polyline;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"U" | "UNDO" if !self.points.is_empty() => Some(self.undo_point()),
|
||||
"C" | "CLOSE" if self.points.len() >= 3 => Some(self.finish_draw()),
|
||||
_ => None,
|
||||
},
|
||||
WipeoutMode::Frames => match text.as_str() {
|
||||
"0" | "OFF" => Some(CmdResult::Dispatch("WIPEOUTFRAME 0".into())),
|
||||
"1" | "ON" => Some(CmdResult::Dispatch("WIPEOUTFRAME 1".into())),
|
||||
"2" | "D" | "DISPLAYBUTNOTPLOT" => {
|
||||
Some(CmdResult::Dispatch("WIPEOUTFRAME 2".into()))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
WipeoutMode::ErasePolyline => match text.as_str() {
|
||||
"Y" | "YES" => Some(self.finish_polyline(true)),
|
||||
"N" | "NO" => Some(self.finish_polyline(false)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -264,250 +344,115 @@ impl CadCommand for WipeoutCommand {
|
||||
false,
|
||||
))
|
||||
}
|
||||
WipeoutMode::Polyline => None,
|
||||
WipeoutMode::Polyline | WipeoutMode::Frames | WipeoutMode::ErasePolyline => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_rect_wipeout(first: DVec3, second: DVec3) -> EntityType {
|
||||
let corner1 = Vector3::new(
|
||||
first.x.min(second.x),
|
||||
first.y.min(second.y),
|
||||
first.z,
|
||||
);
|
||||
let corner2 = Vector3::new(
|
||||
first.x.max(second.x),
|
||||
first.y.max(second.y),
|
||||
first.z,
|
||||
);
|
||||
EntityType::Wipeout(Wipeout::from_corners(corner1, corner2))
|
||||
}
|
||||
|
||||
fn same_point(first: DVec3, second: DVec3) -> bool {
|
||||
(first - second).length_squared() < 1e-18
|
||||
}
|
||||
|
||||
fn clean_boundary(points: &[DVec3]) -> Vec<DVec3> {
|
||||
let mut clean = Vec::with_capacity(points.len());
|
||||
for point in points.iter().copied().filter(|point| point.is_finite()) {
|
||||
if clean.last().is_none_or(|last| !same_point(*last, point)) {
|
||||
clean.push(point);
|
||||
}
|
||||
}
|
||||
if clean.len() > 1 && same_point(clean[0], *clean.last().unwrap()) {
|
||||
clean.pop();
|
||||
}
|
||||
clean
|
||||
}
|
||||
|
||||
fn make_poly_wipeout(points: &[DVec3]) -> Option<EntityType> {
|
||||
let points = clean_boundary(points);
|
||||
if points.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let z = points[0].z;
|
||||
if points.iter().any(|point| (point.z - z).abs() > 1e-7) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut min_x = points[0].x;
|
||||
let mut min_y = points[0].y;
|
||||
let mut max_x = points[0].x;
|
||||
let mut max_y = points[0].y;
|
||||
for point in points.iter().skip(1) {
|
||||
min_x = min_x.min(point.x);
|
||||
min_y = min_y.min(point.y);
|
||||
max_x = max_x.max(point.x);
|
||||
max_y = max_y.max(point.y);
|
||||
}
|
||||
let width = max_x - min_x;
|
||||
let height = max_y - min_y;
|
||||
if width < 1e-9 || height < 1e-9 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Stable signed area around a local origin rejects collinear boundaries
|
||||
// without losing precision on large world coordinates.
|
||||
let origin = points[0];
|
||||
let mut twice_area = 0.0;
|
||||
for index in 0..points.len() {
|
||||
let a = points[index] - origin;
|
||||
let b = points[(index + 1) % points.len()] - origin;
|
||||
twice_area += a.x * b.y - b.x * a.y;
|
||||
}
|
||||
if twice_area.abs() <= width * height * 1e-10 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Wipeout clip vertices use image-pixel coordinates centred on the image:
|
||||
// X grows right, Y grows up while the stored V vector points down.
|
||||
let clip_boundary_vertices = points
|
||||
.iter()
|
||||
.map(|point| {
|
||||
let normalized_x = (point.x - min_x) / width;
|
||||
let normalized_y = (point.y - min_y) / height;
|
||||
Vector2::new(normalized_x - 0.5, 0.5 - normalized_y)
|
||||
impl WipeoutCommand {
|
||||
fn finish_polyline(&self, erase_source: bool) -> CmdResult {
|
||||
self.selected_polyline.map_or(CmdResult::Cancel, |handle| {
|
||||
CmdResult::WipeoutFromPolyline {
|
||||
handle,
|
||||
erase_source,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wipeout_frame_mode(document: &CadDocument) -> i16 {
|
||||
crate::scene::frame::mode(document, crate::scene::frame::FrameKind::Wipeout)
|
||||
}
|
||||
|
||||
fn vector(value: [f64; 3]) -> Vector3 {
|
||||
Vector3::new(value[0], value[1], value[2])
|
||||
}
|
||||
|
||||
fn make_rect_wipeout(first: DVec3, second: DVec3, plane: Plane) -> Option<EntityType> {
|
||||
plane.normal()?;
|
||||
let points = [
|
||||
[first.x, first.y],
|
||||
[second.x, first.y],
|
||||
[second.x, second.y],
|
||||
[first.x, second.y],
|
||||
];
|
||||
let frame = polygon_frame(&points, Tolerance::default())?;
|
||||
let mut wipeout = Wipeout::new();
|
||||
wipeout.insertion_point = Vector3::new(min_x, min_y, z);
|
||||
wipeout.u_vector = Vector3::new(width, 0.0, 0.0);
|
||||
wipeout.v_vector = Vector3::new(0.0, height, 0.0);
|
||||
wipeout.insertion_point = vector(plane.point_at(frame.origin));
|
||||
wipeout.u_vector = vector(plane.x_axis) * frame.size[0];
|
||||
wipeout.v_vector = vector(plane.y_axis) * frame.size[1];
|
||||
Some(EntityType::Wipeout(wipeout))
|
||||
}
|
||||
|
||||
fn make_poly_wipeout(points: &[[f64; 2]], plane: Plane) -> Option<EntityType> {
|
||||
plane.normal()?;
|
||||
let frame = polygon_frame(points, Tolerance::default())?;
|
||||
let [width, height] = frame.size;
|
||||
let mut wipeout = Wipeout::new();
|
||||
wipeout.insertion_point = vector(plane.point_at(frame.origin));
|
||||
wipeout.u_vector = vector(plane.x_axis) * width;
|
||||
wipeout.v_vector = vector(plane.y_axis) * height;
|
||||
wipeout.size = Vector2::new(1.0, 1.0);
|
||||
wipeout.clip_type = WipeoutClipType::Polygonal;
|
||||
wipeout.clip_boundary_vertices = clip_boundary_vertices;
|
||||
wipeout.clip_boundary_vertices = frame
|
||||
.points
|
||||
.iter()
|
||||
.map(|point| {
|
||||
Vector2::new(
|
||||
(point[0] - frame.origin[0]) / width - 0.5,
|
||||
0.5 - (point[1] - frame.origin[1]) / height,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
wipeout.clipping_enabled = true;
|
||||
Some(EntityType::Wipeout(wipeout))
|
||||
}
|
||||
|
||||
fn explicitly_closed(points: &[DVec3]) -> bool {
|
||||
points.len() >= 4 && same_point(points[0], *points.last().unwrap())
|
||||
}
|
||||
|
||||
fn sample_2d_polyline(
|
||||
vertices: &[([f64; 2], f64)],
|
||||
elevation: f64,
|
||||
normal: (f64, f64, f64),
|
||||
) -> Vec<DVec3> {
|
||||
if vertices.len() < 3 {
|
||||
return Vec::new();
|
||||
}
|
||||
let to_wcs = |point: [f64; 2]| {
|
||||
let point = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(point[0], point[1], elevation),
|
||||
normal,
|
||||
);
|
||||
DVec3::new(point.0, point.1, point.2)
|
||||
};
|
||||
let mut points = vec![to_wcs(vertices[0].0)];
|
||||
for index in 0..vertices.len() {
|
||||
let (start, bulge) = vertices[index];
|
||||
let end_index = (index + 1) % vertices.len();
|
||||
let end = vertices[end_index].0;
|
||||
if let Some(arc) =
|
||||
crate::entities::common::BulgeArc::from_bulge(start, end, bulge)
|
||||
{
|
||||
let steps = ((arc.sweep.abs() / std::f64::consts::TAU * 64.0).ceil()
|
||||
as usize)
|
||||
.clamp(4, 64);
|
||||
for step in 1..=steps {
|
||||
if end_index == 0 && step == steps {
|
||||
break;
|
||||
}
|
||||
points.push(to_wcs(arc.sample(step as f64 / steps as f64)));
|
||||
}
|
||||
} else if end_index != 0 {
|
||||
points.push(to_wcs(end));
|
||||
}
|
||||
}
|
||||
points
|
||||
fn explicitly_closed(points: &[[f64; 2]]) -> bool {
|
||||
points.len() >= 4
|
||||
&& cadkernel::geom2d::Vec2::from(points[0])
|
||||
.distance(cadkernel::geom2d::Vec2::from(*points.last().unwrap()))
|
||||
<= Tolerance::default().linear()
|
||||
}
|
||||
|
||||
/// Build a wipeout boundary from a picked closed polyline without consuming
|
||||
/// the source entity. Bulged 2D segments are sampled into the polygonal mask.
|
||||
/// the source entity. Only straight, closed 2D polygonal boundaries qualify.
|
||||
pub(crate) fn wipeout_from_polyline(entity: &EntityType) -> Option<EntityType> {
|
||||
let points = match entity {
|
||||
fn from_ocs(points: &[[f64; 2]], normal: Vector3, elevation: f64) -> Option<EntityType> {
|
||||
make_poly_wipeout(points, crate::entities::curve::ocs_plane(normal, elevation))
|
||||
}
|
||||
|
||||
match entity {
|
||||
EntityType::LwPolyline(polyline) => {
|
||||
let raw: Vec<DVec3> = polyline
|
||||
if polyline.vertices.iter().any(|vertex| vertex.bulge != 0.0) {
|
||||
return None;
|
||||
}
|
||||
let raw: Vec<[f64; 2]> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| DVec3::new(vertex.location.x, vertex.location.y, polyline.elevation))
|
||||
.map(|vertex| [vertex.location.x, vertex.location.y])
|
||||
.collect();
|
||||
if !polyline.is_closed && !explicitly_closed(&raw) {
|
||||
return None;
|
||||
}
|
||||
let vertices: Vec<([f64; 2], f64)> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
(
|
||||
[vertex.location.x, vertex.location.y],
|
||||
vertex.bulge,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
sample_2d_polyline(
|
||||
&vertices,
|
||||
polyline.elevation,
|
||||
(
|
||||
polyline.normal.x,
|
||||
polyline.normal.y,
|
||||
polyline.normal.z,
|
||||
),
|
||||
)
|
||||
from_ocs(&raw, polyline.normal, polyline.elevation)
|
||||
}
|
||||
EntityType::Polyline2D(polyline) => {
|
||||
let raw: Vec<DVec3> = polyline
|
||||
if polyline.vertices.iter().any(|vertex| vertex.bulge != 0.0) {
|
||||
return None;
|
||||
}
|
||||
let raw: Vec<[f64; 2]> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
DVec3::new(
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
polyline.elevation,
|
||||
)
|
||||
})
|
||||
.map(|vertex| [vertex.location.x, vertex.location.y])
|
||||
.collect();
|
||||
if !polyline.is_closed() && !explicitly_closed(&raw) {
|
||||
return None;
|
||||
}
|
||||
let vertices: Vec<([f64; 2], f64)> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
(
|
||||
[vertex.location.x, vertex.location.y],
|
||||
vertex.bulge,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
sample_2d_polyline(
|
||||
&vertices,
|
||||
polyline.elevation,
|
||||
(
|
||||
polyline.normal.x,
|
||||
polyline.normal.y,
|
||||
polyline.normal.z,
|
||||
),
|
||||
)
|
||||
}
|
||||
EntityType::Polyline(polyline) => {
|
||||
let points: Vec<DVec3> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
DVec3::new(
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
vertex.location.z,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
if !polyline.is_closed() && !explicitly_closed(&points) {
|
||||
return None;
|
||||
}
|
||||
points
|
||||
}
|
||||
EntityType::Polyline3D(polyline) => {
|
||||
let points: Vec<DVec3> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
DVec3::new(
|
||||
vertex.position.x,
|
||||
vertex.position.y,
|
||||
vertex.position.z,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
if !polyline.flags.closed && !explicitly_closed(&points) {
|
||||
return None;
|
||||
}
|
||||
points
|
||||
from_ocs(&raw, polyline.normal, polyline.elevation)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
make_poly_wipeout(&points)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
|
||||
@@ -365,6 +365,8 @@ impl CadCommand for AreaCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -88,6 +88,8 @@ impl CadCommand for DistCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -73,6 +73,8 @@ impl MeasureGeomCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -3724,6 +3724,8 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -69,7 +69,7 @@ impl CadModule for InsertModule {
|
||||
),
|
||||
(
|
||||
"FRAMES2",
|
||||
"Frames & Print",
|
||||
"Frames On, Not Plotted",
|
||||
IconKind::Svg(include_bytes!(
|
||||
"../../../assets/icons/underlay_frames.svg"
|
||||
)),
|
||||
|
||||
@@ -514,6 +514,8 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -57,6 +57,8 @@ impl CadCommand for PlotWindowCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -139,6 +139,8 @@ impl CadCommand for ZoomWindowCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
Reference in new issue
Block a user