forked from 1b-agent/opencadstudio-upstream
Merge PR #1106: thicken surfaces with exact kernel geometry
Use the maintained cadkernel main revision and retain source surfaces. Verify signed and two-point input, partial failure, DWG round-trips and one-step undo/redo of solids and history objects.
This commit is contained in:
commit
cf12c6a272
7 files changed
+359
-4
No files matched your search
Generated
+1
-1
@@ -885,7 +885,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cadkernel"
|
name = "cadkernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=f760c46d028d4ea9d4a9834b11e9a85d9d316ad3#f760c46d028d4ea9d4a9834b11e9a85d9d316ad3"
|
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=7f18223c66c1fde2e2a2c16e61259655d173bf4f#7f18223c66c1fde2e2a2c16e61259655d173bf4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ rfd = "0.17"
|
|||||||
clap = { version = "4", features = ["derive", "string"] }
|
clap = { version = "4", features = ["derive", "string"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "62628c41670a914d1598a14faafc3c6e3aa4a270", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "62628c41670a914d1598a14faafc3c6e3aa4a270", features = ["serde"] }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "f760c46d028d4ea9d4a9834b11e9a85d9d316ad3", features = ["acis", "offset"] }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "7f18223c66c1fde2e2a2c16e61259655d173bf4f", features = ["acis", "offset"] }
|
||||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ thiserror = "1"
|
|||||||
|
|
||||||
# Host adapters convert ObjectId to acadrust handles; kernel handles stay local.
|
# Host adapters convert ObjectId to acadrust handles; kernel handles stay local.
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "62628c41670a914d1598a14faafc3c6e3aa4a270", features = ["serde"], optional = true }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "62628c41670a914d1598a14faafc3c6e3aa4a270", features = ["serde"], optional = true }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "f760c46d028d4ea9d4a9834b11e9a85d9d316ad3", features = ["acis", "offset"], optional = true }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "7f18223c66c1fde2e2a2c16e61259655d173bf4f", features = ["acis", "offset"], optional = true }
|
||||||
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
|
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|||||||
@@ -3803,6 +3803,102 @@ impl OpenCADStudio {
|
|||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CmdResult::ThickenEntities { handles, distance } => {
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
|
||||||
|
if handles.is_empty() {
|
||||||
|
self.command_line
|
||||||
|
.push_output(crate::t!("No surfaces selected.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
if !distance.is_finite() {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("Requires numeric distance or two points.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
if distance.abs() <= f64::EPSILON {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||||
|
let pending = self.begin_undo(i, "THICKEN", handles.len(), true);
|
||||||
|
let mut created = 0usize;
|
||||||
|
let mut failed = 0usize;
|
||||||
|
let mut self_intersections = 0usize;
|
||||||
|
for handle in handles {
|
||||||
|
let Some(acadrust::EntityType::Surface(surface)) =
|
||||||
|
self.tabs[i].scene.document.get_entity(handle)
|
||||||
|
else {
|
||||||
|
failed += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let kernel_distance = if surface.kind
|
||||||
|
== acadrust::entities::SurfaceKind::Revolved
|
||||||
|
{
|
||||||
|
-distance
|
||||||
|
} else {
|
||||||
|
distance
|
||||||
|
};
|
||||||
|
let body = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.solid_models
|
||||||
|
.get(&handle)
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| {
|
||||||
|
crate::scene::convert::solid3d_tess::kernel_surface_body(surface)
|
||||||
|
});
|
||||||
|
let Some(body) = body else {
|
||||||
|
failed += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let solid = match cadkernel::brep::thicken(&body, kernel_distance) {
|
||||||
|
Ok(solid) => solid,
|
||||||
|
Err(cadkernel::brep::ThickenError::SelfIntersection) => {
|
||||||
|
failed += 1;
|
||||||
|
self_intersections += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
failed += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let history = crate::scene::model::solid_history::brep_op(&solid);
|
||||||
|
if self
|
||||||
|
.add_solid_model(empty_solid3d(), solid, history)
|
||||||
|
.is_null()
|
||||||
|
{
|
||||||
|
failed += 1;
|
||||||
|
} else {
|
||||||
|
created += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if created > 0 {
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
}
|
||||||
|
if self_intersections > 0 {
|
||||||
|
self.command_line.push_error(&crate::tf!(
|
||||||
|
"{} surface(s) cannot be thickened because the offset intersects itself.",
|
||||||
|
self_intersections
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if failed > 0 {
|
||||||
|
self.command_line.push_output(&crate::tf!(
|
||||||
|
"THICKEN: created {} solid(s); {} surface(s) could not be thickened exactly.",
|
||||||
|
created, failed
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(pending) = pending {
|
||||||
|
self.commit_undo_delta(i, pending);
|
||||||
|
}
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
|
||||||
CmdResult::PresspullPick { handle, point, offset, multiple: _ } => {
|
CmdResult::PresspullPick { handle, point, offset, multiple: _ } => {
|
||||||
self.presspull_pick(handle, point, offset);
|
self.presspull_pick(handle, point, offset);
|
||||||
}
|
}
|
||||||
@@ -5713,3 +5809,52 @@ fn resample_widths(source: &[(f64, f64)], count: usize) -> Vec<(f64, f64)> {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||||
|
mod thicken_tests {
|
||||||
|
use super::*;
|
||||||
|
use acadrust::entities::{Surface, SurfaceKind};
|
||||||
|
use cadkernel::geom2d::{Circle, Curve};
|
||||||
|
use cadkernel::space::Plane;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thicken_preserves_sources_and_round_trips_results_with_one_undo() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
let body = cadkernel::brep::planar_region(Plane::XY, &[
|
||||||
|
vec![Curve::Circle(Circle { centre: [0.0; 2], radius: 3.0 })],
|
||||||
|
]).unwrap();
|
||||||
|
let source = app.add_surface_model(acadrust::EntityType::Surface(Surface::new(SurfaceKind::Plane)), body);
|
||||||
|
assert!(!source.is_null());
|
||||||
|
let i = app.active_tab;
|
||||||
|
let invalid = app.tabs[i].scene.add_entity(acadrust::EntityType::Surface(Surface::new(SurfaceKind::Generic)));
|
||||||
|
let original = serde_json::to_value(app.tabs[i].scene.document.get_entity(source).unwrap()).unwrap();
|
||||||
|
let before = app.tabs[i].history.undo_stack.len();
|
||||||
|
let _ = app.apply_cmd_result(CmdResult::ThickenEntities { handles: vec![source], distance: 0.0 });
|
||||||
|
assert_eq!(app.tabs[i].history.undo_stack.len(), before);
|
||||||
|
let _ = app.apply_cmd_result(CmdResult::ThickenEntities { handles: vec![invalid, source], distance: 2.0 });
|
||||||
|
assert_eq!(app.tabs[i].history.undo_stack.len(), before + 1);
|
||||||
|
let result = app.tabs[i].scene.document.entities().find_map(|entity| {
|
||||||
|
matches!(entity, acadrust::EntityType::Solid3D(_)).then_some(entity.common().handle)
|
||||||
|
}).unwrap();
|
||||||
|
assert!(app.tabs[i].scene.document.solid_history_operation(result).is_some());
|
||||||
|
assert_eq!(serde_json::to_value(app.tabs[i].scene.document.get_entity(source).unwrap()).unwrap(), original);
|
||||||
|
let expected = cadkernel::brep::analytic_mass_properties(&app.tabs[i].scene.solid_models[&result]).unwrap().volume;
|
||||||
|
let bytes = crate::io::save_to_bytes(&app.tabs[i].scene.document, "dwg", app.tabs[i].scene.document.version).unwrap();
|
||||||
|
let document = crate::io::load_bytes("thicken.dwg", bytes).unwrap();
|
||||||
|
let mut restored = crate::scene::Scene::new();
|
||||||
|
restored.document = document;
|
||||||
|
restored.restore_solid_models(&[result]);
|
||||||
|
let actual = cadkernel::brep::analytic_mass_properties(&restored.solid_models[&result]).unwrap().volume;
|
||||||
|
assert!((expected - actual).abs() < 1e-8);
|
||||||
|
app.undo_active_tab();
|
||||||
|
assert!(app.tabs[i].scene.document.get_entity(result).is_none());
|
||||||
|
assert!(app.tabs[i].scene.document.solid_history_operation(result).is_none());
|
||||||
|
assert!(app.tabs[i].scene.document.get_entity(source).is_some());
|
||||||
|
assert!(app.tabs[i].scene.document.get_entity(invalid).is_some());
|
||||||
|
app.redo_active_tab();
|
||||||
|
assert!(app.tabs[i].scene.document.get_entity(result).is_some());
|
||||||
|
assert!(app.tabs[i].scene.document.solid_history_operation(result).is_some());
|
||||||
|
assert_eq!(serde_json::to_value(app.tabs[i].scene.document.get_entity(source).unwrap()).unwrap(), original);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -734,7 +734,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── EXTRUDE ────────────────────────────────────────────────────
|
// ── EXTRUDE ────────────────────────────────────────────────────
|
||||||
"EXTRUDE" | "THICKEN" => {
|
"EXTRUDE" => {
|
||||||
use crate::modules::insert::solid3d_cmds::ExtrudeCommand;
|
use crate::modules::insert::solid3d_cmds::ExtrudeCommand;
|
||||||
// A preselection becomes the complete source set; otherwise
|
// A preselection becomes the complete source set; otherwise
|
||||||
// the interactive command gathers any number of profiles.
|
// the interactive command gathers any number of profiles.
|
||||||
@@ -769,6 +769,19 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"THICKEN" => {
|
||||||
|
use crate::modules::insert::solid3d_cmds::ThickenCommand;
|
||||||
|
let selected = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.selected_entities()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(handle, entity)| (handle, entity.clone()))
|
||||||
|
.collect();
|
||||||
|
let command = ThickenCommand::new(selected);
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
"PRESSPULL" => {
|
"PRESSPULL" => {
|
||||||
use crate::modules::insert::solid3d_cmds::PresspullCommand;
|
use crate::modules::insert::solid3d_cmds::PresspullCommand;
|
||||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||||
|
|||||||
@@ -1518,6 +1518,11 @@ pub enum CmdResult {
|
|||||||
taper_angle: f64,
|
taper_angle: f64,
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
},
|
},
|
||||||
|
/// Thicken one or more persistent surface bodies without consuming them.
|
||||||
|
ThickenEntities {
|
||||||
|
handles: Vec<Handle>,
|
||||||
|
distance: f64,
|
||||||
|
},
|
||||||
/// Resolve a profile, bounded area, or solid face for PRESSPULL.
|
/// Resolve a profile, bounded area, or solid face for PRESSPULL.
|
||||||
PresspullPick {
|
PresspullPick {
|
||||||
handle: Option<Handle>,
|
handle: Option<Handle>,
|
||||||
|
|||||||
@@ -513,6 +513,172 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── THICKEN command ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub struct ThickenCommand {
|
||||||
|
step: ThickenStep,
|
||||||
|
handles: Vec<Handle>,
|
||||||
|
first_point: Option<DVec3>,
|
||||||
|
last_distance: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum ThickenStep {
|
||||||
|
Pick,
|
||||||
|
Distance,
|
||||||
|
SecondPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thicken_default() -> &'static Mutex<f64> {
|
||||||
|
static DEFAULT: OnceLock<Mutex<f64>> = OnceLock::new();
|
||||||
|
DEFAULT.get_or_init(|| Mutex::new(0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThickenCommand {
|
||||||
|
pub fn new(preselection: Vec<(Handle, EntityType)>) -> Self {
|
||||||
|
let handles = preselection
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(handle, entity)| {
|
||||||
|
matches!(entity, EntityType::Surface(_)).then_some(handle)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Self {
|
||||||
|
step: if handles.is_empty() {
|
||||||
|
ThickenStep::Pick
|
||||||
|
} else {
|
||||||
|
ThickenStep::Distance
|
||||||
|
},
|
||||||
|
handles,
|
||||||
|
first_point: None,
|
||||||
|
last_distance: *thicken_default()
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&mut self, distance: f64) -> CmdResult {
|
||||||
|
self.last_distance = distance;
|
||||||
|
*thicken_default()
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner()) = distance;
|
||||||
|
CmdResult::ThickenEntities {
|
||||||
|
handles: self.handles.clone(),
|
||||||
|
distance,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CadCommand for ThickenCommand {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"THICKEN"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt(&self) -> String {
|
||||||
|
match self.step {
|
||||||
|
ThickenStep::Pick => t!("Select surfaces to thicken:").into_owned(),
|
||||||
|
ThickenStep::Distance => format!(
|
||||||
|
"{} <{}>:",
|
||||||
|
t!("Specify thickness"),
|
||||||
|
crate::entities::common::format_length(self.last_distance)
|
||||||
|
),
|
||||||
|
ThickenStep::SecondPoint => {
|
||||||
|
t!("Specify second point:").into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wants_text_input(&self) -> bool {
|
||||||
|
self.step == ThickenStep::Distance
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||||
|
if self.step != ThickenStep::Distance {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let value = text.trim();
|
||||||
|
if value.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(match crate::entities::common::parse_typed_length(value) {
|
||||||
|
Some(distance) if distance.is_finite() => self.finish(distance),
|
||||||
|
_ => CmdResult::ReportError(
|
||||||
|
t!("Requires numeric distance or two points.").into_owned(),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||||
|
match self.step {
|
||||||
|
ThickenStep::Distance => {
|
||||||
|
self.first_point = Some(point);
|
||||||
|
self.step = ThickenStep::SecondPoint;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
ThickenStep::SecondPoint => {
|
||||||
|
let distance = point.distance(self.first_point.unwrap_or(point));
|
||||||
|
if distance.is_finite() {
|
||||||
|
self.finish(distance)
|
||||||
|
} else {
|
||||||
|
CmdResult::ReportError(
|
||||||
|
t!("Requires numeric distance or two points.").into_owned(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThickenStep::Pick => CmdResult::NeedPoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
|
match self.step {
|
||||||
|
ThickenStep::Pick if self.handles.is_empty() => {
|
||||||
|
CmdResult::Measurement(t!("No surfaces selected.").into_owned())
|
||||||
|
}
|
||||||
|
ThickenStep::Pick => {
|
||||||
|
self.step = ThickenStep::Distance;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
ThickenStep::Distance => self.finish(self.last_distance),
|
||||||
|
ThickenStep::SecondPoint => CmdResult::ReportError(
|
||||||
|
t!("Requires numeric distance or two points.").into_owned(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_selection_gathering(&self) -> bool {
|
||||||
|
self.step == ThickenStep::Pick
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_forces_add(&self) -> bool {
|
||||||
|
self.step == ThickenStep::Pick
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_selection_entities(&mut self, entities: Vec<SelectionEntity>) {
|
||||||
|
if self.step != ThickenStep::Pick {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.handles = entities
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|entry| {
|
||||||
|
matches!(entry.entity, EntityType::Surface(_)).then_some(entry.handle)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_selection_complete(&mut self, _handles: Vec<Handle>) -> CmdResult {
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_undo_step(&mut self) -> Option<CmdResult> {
|
||||||
|
if self.step == ThickenStep::SecondPoint {
|
||||||
|
self.first_point = None;
|
||||||
|
self.step = ThickenStep::Distance;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── PRESSPULL command ─────────────────────────────────────────────────────
|
// ── PRESSPULL command ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct PresspullCommand {
|
pub struct PresspullCommand {
|
||||||
@@ -2701,3 +2867,29 @@ mod revolve_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
inventory::submit!(crate::command::CommandRegistration { names: &["SWEEP"] });
|
inventory::submit!(crate::command::CommandRegistration { names: &["SWEEP"] });
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod thicken_command_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thicken_filters_selection_recovers_from_invalid_text_and_measures_two_points() {
|
||||||
|
let handle = Handle::new(42);
|
||||||
|
let mut command = ThickenCommand::new(vec![
|
||||||
|
(Handle::new(1), EntityType::Line(acadrust::entities::Line::default())),
|
||||||
|
(handle, EntityType::Surface(acadrust::entities::Surface::new(acadrust::entities::SurfaceKind::Plane))),
|
||||||
|
]);
|
||||||
|
assert!(!command.is_selection_gathering());
|
||||||
|
assert_eq!(command.handles, vec![handle]);
|
||||||
|
assert!(matches!(command.on_text_input("bad"), Some(CmdResult::ReportError(_))));
|
||||||
|
assert!(command.input_kind().wants_text());
|
||||||
|
assert!(matches!(command.on_text_input("-2"), Some(CmdResult::ThickenEntities { distance: -2.0, .. })));
|
||||||
|
assert!(matches!(command.on_point(DVec3::ZERO), CmdResult::NeedPoint));
|
||||||
|
assert!(command.on_undo_step().is_some());
|
||||||
|
let _ = command.on_point(DVec3::ZERO);
|
||||||
|
assert!(matches!(command.on_point(DVec3::new(3.0, 4.0, 0.0)), CmdResult::ThickenEntities { distance: 5.0, .. }));
|
||||||
|
let mut empty = ThickenCommand::new(Vec::new());
|
||||||
|
assert!(empty.is_selection_gathering());
|
||||||
|
assert!(matches!(empty.on_enter(), CmdResult::Measurement(_)));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user