forked from 1b-agent/opencadstudio-upstream
Align EXTRUDE command state and path behavior
This commit is contained in:
1 parent
847d218c35
commit
d23a910de7
6 files changed
+294
-75
No files matched your search
Generated
+1
-1
@@ -878,7 +878,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=1ad942cc34b592917ca5fff9936afb902109a29d#1ad942cc34b592917ca5fff9936afb902109a29d"
|
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=7d7f8dabe64cbacdeea7063d3ebde5d170f9a365#7d7f8dabe64cbacdeea7063d3ebde5d170f9a365"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ rfd = "0.17"
|
|||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "a0f7d444f1607bc4b2c881060cbe7ea1014253cb", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "a0f7d444f1607bc4b2c881060cbe7ea1014253cb", features = ["serde"] }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "1ad942cc34b592917ca5fff9936afb902109a29d", features = ["acis", "offset"] }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "7d7f8dabe64cbacdeea7063d3ebde5d170f9a365", 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"] }
|
||||||
|
|||||||
+36
-30
@@ -3476,13 +3476,12 @@ impl OpenCADStudio {
|
|||||||
};
|
};
|
||||||
use crate::scene::model::sweep_model;
|
use crate::scene::model::sweep_model;
|
||||||
let path = match extent {
|
let path = match extent {
|
||||||
ExtrudeExtent::Path(handle) => {
|
ExtrudeExtent::Path(handle) => self.tabs[i]
|
||||||
if delete_sources && self.reject_locked_edit(i, handle) {
|
.scene
|
||||||
self.tabs[i].active_cmd = None;
|
.document
|
||||||
return Task::none();
|
.get_entity(handle)
|
||||||
}
|
.cloned()
|
||||||
self.tabs[i].scene.document.get_entity(handle).cloned().map(|entity| (handle, entity))
|
.map(|entity| (handle, entity)),
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if matches!(extent, ExtrudeExtent::Path(_)) && path.is_none() {
|
if matches!(extent, ExtrudeExtent::Path(_)) && path.is_none() {
|
||||||
@@ -3515,22 +3514,16 @@ impl OpenCADStudio {
|
|||||||
ExtrudeExtent::Direction(direction) => Some(direction),
|
ExtrudeExtent::Direction(direction) => Some(direction),
|
||||||
ExtrudeExtent::Path(_) => None,
|
ExtrudeExtent::Path(_) => None,
|
||||||
};
|
};
|
||||||
let path_direction = path.as_ref().and_then(|(_, path)| {
|
let path_direction = path
|
||||||
crate::entities::curve::entity_curve(path).and_then(|curve| {
|
.as_ref()
|
||||||
let cadkernel::geom2d::Curve::Line(line) = curve.curve else {
|
.and_then(|(_, path)| sweep_model::straight_path_direction(path))
|
||||||
return None;
|
.map(glam::DVec3::from_array);
|
||||||
};
|
|
||||||
let start = glam::DVec3::from_array(curve.plane.point_at(line.start));
|
|
||||||
let end = glam::DVec3::from_array(curve.plane.point_at(line.end));
|
|
||||||
Some(end - start)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
// The creation mode controls closed profiles. Open curves
|
// The creation mode controls closed profiles. Open curves
|
||||||
// always create sheet surfaces.
|
// always create sheet surfaces.
|
||||||
let creates_surface = matches!(mode, ExtrudeMode::Surface) || !closed;
|
let creates_surface = matches!(mode, ExtrudeMode::Surface) || !closed;
|
||||||
let body = match (creates_surface, &path, direction) {
|
let body = match (creates_surface, &path, direction) {
|
||||||
(false, Some((_, path)), _) if taper_angle.abs() <= 1e-12 => {
|
(false, Some((_, path)), _) => {
|
||||||
sweep_model::extruded_along_path(&entity, path)
|
sweep_model::extruded_along_path(&entity, path, taper_angle)
|
||||||
}
|
}
|
||||||
(false, None, Some(direction)) => {
|
(false, None, Some(direction)) => {
|
||||||
sweep_model::extruded_direction(
|
sweep_model::extruded_direction(
|
||||||
@@ -3547,7 +3540,11 @@ impl OpenCADStudio {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
(true, Some(_), _) => path_direction.and_then(|direction| {
|
(true, Some(_), _) => path_direction.and_then(|direction| {
|
||||||
sweep_model::extruded_surface(&entity, direction.to_array(), 0.0)
|
sweep_model::extruded_surface(
|
||||||
|
&entity,
|
||||||
|
direction.to_array(),
|
||||||
|
taper_angle,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
@@ -3565,13 +3562,25 @@ impl OpenCADStudio {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let direction = direction.unwrap_or(glam::DVec3::ZERO);
|
let direction = direction.unwrap_or(glam::DVec3::ZERO);
|
||||||
let history = sweep_model::extrusion_history(
|
let history = path
|
||||||
&entity,
|
.as_ref()
|
||||||
path.as_ref().map(|(_, entity)| entity),
|
.and_then(|(_, path)| {
|
||||||
direction.to_array(),
|
sweep_model::sweep_history(
|
||||||
taper_angle,
|
&entity,
|
||||||
profile.plane.origin,
|
path,
|
||||||
)
|
taper_angle,
|
||||||
|
profile.plane.origin,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
sweep_model::extrusion_history(
|
||||||
|
&entity,
|
||||||
|
None,
|
||||||
|
direction.to_array(),
|
||||||
|
taper_angle,
|
||||||
|
profile.plane.origin,
|
||||||
|
)
|
||||||
|
})
|
||||||
.unwrap_or_else(|| crate::scene::model::solid_history::brep_op(&body));
|
.unwrap_or_else(|| crate::scene::model::solid_history::brep_op(&body));
|
||||||
self.add_solid_model(empty_solid3d(), body, history)
|
self.add_solid_model(empty_solid3d(), body, history)
|
||||||
};
|
};
|
||||||
@@ -3585,9 +3594,6 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if delete_sources && !created_handles.is_empty() {
|
if delete_sources && !created_handles.is_empty() {
|
||||||
if let Some((path_handle, _)) = path {
|
|
||||||
consumed.push(path_handle);
|
|
||||||
}
|
|
||||||
consumed.sort_unstable_by_key(|handle| handle.value());
|
consumed.sort_unstable_by_key(|handle| handle.value());
|
||||||
consumed.dedup();
|
consumed.dedup();
|
||||||
self.tabs[i].scene.erase_entities(&consumed);
|
self.tabs[i].scene.erase_entities(&consumed);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// Profile-based kernel solid commands stored as exact ACIS data.
|
// Profile-based kernel solid commands stored as exact ACIS data.
|
||||||
|
|
||||||
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
use acadrust::{entities::Solid3D, EntityType};
|
use acadrust::{entities::Solid3D, EntityType};
|
||||||
use glam::DVec3;
|
use glam::DVec3;
|
||||||
|
|
||||||
@@ -17,9 +19,29 @@ pub struct ExtrudeCommand {
|
|||||||
direction_start: Option<DVec3>,
|
direction_start: Option<DVec3>,
|
||||||
mode: ExtrudeMode,
|
mode: ExtrudeMode,
|
||||||
taper_angle: f64,
|
taper_angle: f64,
|
||||||
|
last_height: Option<f64>,
|
||||||
|
taper_return: ExtrudeStep,
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct ExtrudeDefaults {
|
||||||
|
mode: ExtrudeMode,
|
||||||
|
height: Option<f64>,
|
||||||
|
taper_angle: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extrude_defaults() -> &'static Mutex<ExtrudeDefaults> {
|
||||||
|
static DEFAULTS: OnceLock<Mutex<ExtrudeDefaults>> = OnceLock::new();
|
||||||
|
DEFAULTS.get_or_init(|| {
|
||||||
|
Mutex::new(ExtrudeDefaults {
|
||||||
|
mode: ExtrudeMode::Solid,
|
||||||
|
height: None,
|
||||||
|
taper_angle: 0.0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
enum ExtrudeStep {
|
enum ExtrudeStep {
|
||||||
Pick,
|
Pick,
|
||||||
@@ -30,10 +52,12 @@ enum ExtrudeStep {
|
|||||||
Path,
|
Path,
|
||||||
Taper,
|
Taper,
|
||||||
Expression,
|
Expression,
|
||||||
|
TaperExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtrudeCommand {
|
impl ExtrudeCommand {
|
||||||
pub fn new_named(name: &str, color: [f32; 4]) -> Self {
|
pub fn new_named(name: &str, color: [f32; 4]) -> Self {
|
||||||
|
let defaults = *extrude_defaults().lock().unwrap_or_else(|error| error.into_inner());
|
||||||
Self {
|
Self {
|
||||||
command_name: match name {
|
command_name: match name {
|
||||||
"THICKEN" => "THICKEN",
|
"THICKEN" => "THICKEN",
|
||||||
@@ -44,8 +68,10 @@ impl ExtrudeCommand {
|
|||||||
anchor: DVec3::ZERO,
|
anchor: DVec3::ZERO,
|
||||||
profile_direction: None,
|
profile_direction: None,
|
||||||
direction_start: None,
|
direction_start: None,
|
||||||
mode: ExtrudeMode::Solid,
|
mode: defaults.mode,
|
||||||
taper_angle: 0.0,
|
taper_angle: defaults.taper_angle,
|
||||||
|
last_height: defaults.height,
|
||||||
|
taper_return: ExtrudeStep::Height,
|
||||||
color,
|
color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +91,16 @@ impl ExtrudeCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, extent: ExtrudeExtent) -> CmdResult {
|
fn finish(&self, extent: ExtrudeExtent) -> CmdResult {
|
||||||
|
if let Some(height) = match extent {
|
||||||
|
ExtrudeExtent::Height(height) => Some(height),
|
||||||
|
ExtrudeExtent::Direction(direction) => Some(direction.length()),
|
||||||
|
ExtrudeExtent::Path(_) => None,
|
||||||
|
} {
|
||||||
|
extrude_defaults()
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.height = Some(height);
|
||||||
|
}
|
||||||
CmdResult::ExtrudeEntities {
|
CmdResult::ExtrudeEntities {
|
||||||
handles: self.handles.clone(),
|
handles: self.handles.clone(),
|
||||||
extent,
|
extent,
|
||||||
@@ -82,13 +118,28 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
match self.step {
|
match self.step {
|
||||||
ExtrudeStep::Pick => t!("EXTRUDE Select objects to extrude or [Mode] (Enter to finish):").into_owned(),
|
ExtrudeStep::Pick => t!("EXTRUDE Select objects to extrude or [Mode] (Enter to finish):").into_owned(),
|
||||||
ExtrudeStep::Mode => t!("EXTRUDE Creation mode [Solid/Surface]:").into_owned(),
|
ExtrudeStep::Mode => format!(
|
||||||
ExtrudeStep::Height => t!("EXTRUDE Specify height or [Direction/Path/Taper angle/Expression]:").into_owned(),
|
"{} <{}>:",
|
||||||
|
t!("EXTRUDE Creation mode [Solid/Surface]"),
|
||||||
|
if self.mode == ExtrudeMode::Solid { "Solid" } else { "Surface" }
|
||||||
|
),
|
||||||
|
ExtrudeStep::Height => {
|
||||||
|
let base = t!("EXTRUDE Specify height or [Direction/Path/Taper angle/Expression]");
|
||||||
|
self.last_height.map_or_else(
|
||||||
|
|| format!("{base}:"),
|
||||||
|
|height| format!("{base} <{}>:", crate::entities::common::format_length(height)),
|
||||||
|
)
|
||||||
|
}
|
||||||
ExtrudeStep::DirectionStart => t!("EXTRUDE Start point of direction:").into_owned(),
|
ExtrudeStep::DirectionStart => t!("EXTRUDE Start point of direction:").into_owned(),
|
||||||
ExtrudeStep::DirectionEnd => t!("EXTRUDE End point of direction:").into_owned(),
|
ExtrudeStep::DirectionEnd => t!("EXTRUDE End point of direction:").into_owned(),
|
||||||
ExtrudeStep::Path => t!("EXTRUDE Select extrusion path:").into_owned(),
|
ExtrudeStep::Path => t!("EXTRUDE Select extrusion path or [Taper angle]:").into_owned(),
|
||||||
ExtrudeStep::Taper => t!("EXTRUDE Specify taper angle:").into_owned(),
|
ExtrudeStep::Taper => format!(
|
||||||
|
"{} <{}>:",
|
||||||
|
t!("EXTRUDE Specify taper angle or [Expression]"),
|
||||||
|
crate::entities::common::format_angle(self.taper_angle)
|
||||||
|
),
|
||||||
ExtrudeStep::Expression => t!("EXTRUDE Enter height expression:").into_owned(),
|
ExtrudeStep::Expression => t!("EXTRUDE Enter height expression:").into_owned(),
|
||||||
|
ExtrudeStep::TaperExpression => t!("EXTRUDE Enter taper angle expression:").into_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn options(&self) -> Vec<CmdOption> {
|
fn options(&self) -> Vec<CmdOption> {
|
||||||
@@ -104,6 +155,8 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
CmdOption::new("Taper angle", "TAPER"),
|
CmdOption::new("Taper angle", "TAPER"),
|
||||||
CmdOption::new("Expression", "EXPRESSION"),
|
CmdOption::new("Expression", "EXPRESSION"),
|
||||||
],
|
],
|
||||||
|
ExtrudeStep::Path => vec![CmdOption::new("Taper angle", "TAPER")],
|
||||||
|
ExtrudeStep::Taper => vec![CmdOption::new("Expression", "EXPRESSION")],
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,12 +224,14 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
ExtrudeStep::Pick
|
ExtrudeStep::Pick
|
||||||
| ExtrudeStep::Mode
|
| ExtrudeStep::Mode
|
||||||
| ExtrudeStep::Height
|
| ExtrudeStep::Height
|
||||||
|
| ExtrudeStep::Path
|
||||||
| ExtrudeStep::Taper
|
| ExtrudeStep::Taper
|
||||||
| ExtrudeStep::Expression
|
| ExtrudeStep::Expression
|
||||||
|
| ExtrudeStep::TaperExpression
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn point_step_accepts_keywords(&self) -> bool {
|
fn point_step_accepts_keywords(&self) -> bool {
|
||||||
self.step == ExtrudeStep::Height
|
matches!(self.step, ExtrudeStep::Height | ExtrudeStep::Path)
|
||||||
}
|
}
|
||||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||||
let value = text.trim();
|
let value = text.trim();
|
||||||
@@ -194,6 +249,10 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
} else {
|
} else {
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
}
|
}
|
||||||
|
extrude_defaults()
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.mode = self.mode;
|
||||||
self.step = ExtrudeStep::Pick;
|
self.step = ExtrudeStep::Pick;
|
||||||
Some(CmdResult::NeedPoint)
|
Some(CmdResult::NeedPoint)
|
||||||
}
|
}
|
||||||
@@ -208,6 +267,7 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
}
|
}
|
||||||
if "TAPER".starts_with(&upper) || "TAPER ANGLE".starts_with(&upper) {
|
if "TAPER".starts_with(&upper) || "TAPER ANGLE".starts_with(&upper) {
|
||||||
|
self.taper_return = ExtrudeStep::Height;
|
||||||
self.step = ExtrudeStep::Taper;
|
self.step = ExtrudeStep::Taper;
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
}
|
}
|
||||||
@@ -219,27 +279,71 @@ impl CadCommand for ExtrudeCommand {
|
|||||||
.filter(|height| height.is_finite() && height.abs() > 1e-6)
|
.filter(|height| height.is_finite() && height.abs() > 1e-6)
|
||||||
.map(|height| self.finish(ExtrudeExtent::Height(height)))
|
.map(|height| self.finish(ExtrudeExtent::Height(height)))
|
||||||
}
|
}
|
||||||
|
ExtrudeStep::Path => {
|
||||||
|
let upper = value.to_ascii_uppercase();
|
||||||
|
if "TAPER".starts_with(&upper) || "TAPER ANGLE".starts_with(&upper) {
|
||||||
|
self.taper_return = ExtrudeStep::Path;
|
||||||
|
self.step = ExtrudeStep::Taper;
|
||||||
|
}
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
ExtrudeStep::Taper => {
|
ExtrudeStep::Taper => {
|
||||||
|
if "EXPRESSION".starts_with(&value.to_ascii_uppercase()) {
|
||||||
|
self.step = ExtrudeStep::TaperExpression;
|
||||||
|
return Some(CmdResult::NeedPoint);
|
||||||
|
}
|
||||||
let angle = crate::entities::common::parse_angle(value)?;
|
let angle = crate::entities::common::parse_angle(value)?;
|
||||||
if !angle.is_finite() || angle.abs() >= std::f64::consts::FRAC_PI_2 {
|
if !angle.is_finite() || angle.abs() >= std::f64::consts::FRAC_PI_2 {
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
}
|
}
|
||||||
self.taper_angle = angle;
|
self.taper_angle = angle;
|
||||||
self.step = ExtrudeStep::Height;
|
extrude_defaults()
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.taper_angle = angle;
|
||||||
|
self.step = self.taper_return;
|
||||||
Some(CmdResult::NeedPoint)
|
Some(CmdResult::NeedPoint)
|
||||||
}
|
}
|
||||||
ExtrudeStep::Expression => crate::app::expr_eval::eval_number(value)
|
ExtrudeStep::Expression => crate::app::expr_eval::eval_number(value)
|
||||||
.filter(|height| height.is_finite() && height.abs() > 1e-6)
|
.filter(|height| height.is_finite() && height.abs() > 1e-6)
|
||||||
.map(|height| self.finish(ExtrudeExtent::Height(height))),
|
.map(|height| self.finish(ExtrudeExtent::Height(height))),
|
||||||
|
ExtrudeStep::TaperExpression => {
|
||||||
|
let angle = crate::app::expr_eval::eval_number(value)
|
||||||
|
.and_then(|value| crate::entities::common::parse_angle(&value.to_string()))?;
|
||||||
|
if !angle.is_finite() || angle.abs() >= std::f64::consts::FRAC_PI_2 {
|
||||||
|
return Some(CmdResult::NeedPoint);
|
||||||
|
}
|
||||||
|
self.taper_angle = angle;
|
||||||
|
extrude_defaults()
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.taper_angle = angle;
|
||||||
|
self.step = self.taper_return;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn on_enter(&mut self) -> CmdResult {
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
if self.step == ExtrudeStep::Pick && !self.handles.is_empty() {
|
match self.step {
|
||||||
self.step = ExtrudeStep::Height;
|
ExtrudeStep::Pick if !self.handles.is_empty() => {
|
||||||
CmdResult::NeedPoint
|
self.step = ExtrudeStep::Height;
|
||||||
} else {
|
CmdResult::NeedPoint
|
||||||
CmdResult::Cancel
|
}
|
||||||
|
ExtrudeStep::Mode => {
|
||||||
|
self.step = ExtrudeStep::Pick;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
ExtrudeStep::Height => self
|
||||||
|
.last_height
|
||||||
|
.filter(|height| height.is_finite() && height.abs() > 1e-6)
|
||||||
|
.map(|height| self.finish(ExtrudeExtent::Height(height)))
|
||||||
|
.unwrap_or(CmdResult::Cancel),
|
||||||
|
ExtrudeStep::Taper => {
|
||||||
|
self.step = self.taper_return;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
_ => CmdResult::Cancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn cursor_axis(&self) -> Option<(DVec3, DVec3)> {
|
fn cursor_axis(&self) -> Option<(DVec3, DVec3)> {
|
||||||
|
|||||||
@@ -323,7 +323,30 @@ fn sweep_path_length(value: &SolidHistorySweep) -> Option<f64> {
|
|||||||
use acadrust::entities::EmbeddedEntity;
|
use acadrust::entities::EmbeddedEntity;
|
||||||
use acadrust::EntityType;
|
use acadrust::EntityType;
|
||||||
|
|
||||||
let entity = match value.path_entity.as_ref()? {
|
let path_entity = value.path_entity.as_ref()?;
|
||||||
|
if let EmbeddedEntity::Spline(path) = path_entity {
|
||||||
|
if path.degree == 1 && path.control_points.len() >= 2 && path.weights.is_empty() {
|
||||||
|
let transform = glam::DMat4::from_cols_array(&value.path_entity_transform);
|
||||||
|
if !transform.is_finite() || transform.determinant().abs() <= 1e-12 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(
|
||||||
|
path.control_points
|
||||||
|
.windows(2)
|
||||||
|
.map(|pair| {
|
||||||
|
let start = transform.transform_point3(glam::DVec3::new(
|
||||||
|
pair[0].x, pair[0].y, pair[0].z,
|
||||||
|
));
|
||||||
|
let end = transform.transform_point3(glam::DVec3::new(
|
||||||
|
pair[1].x, pair[1].y, pair[1].z,
|
||||||
|
));
|
||||||
|
end.distance(start)
|
||||||
|
})
|
||||||
|
.sum(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let entity = match path_entity {
|
||||||
EmbeddedEntity::Line(value) => EntityType::Line(value.clone()),
|
EmbeddedEntity::Line(value) => EntityType::Line(value.clone()),
|
||||||
EmbeddedEntity::Arc(value) => EntityType::Arc(value.clone()),
|
EmbeddedEntity::Arc(value) => EntityType::Arc(value.clone()),
|
||||||
EmbeddedEntity::Circle(value) => EntityType::Circle(value.clone()),
|
EmbeddedEntity::Circle(value) => EntityType::Circle(value.clone()),
|
||||||
@@ -1076,7 +1099,7 @@ fn apply_extrusion_geometry_property(
|
|||||||
}
|
}
|
||||||
PROP_EXTRUSION_HEIGHT => {
|
PROP_EXTRUSION_HEIGHT => {
|
||||||
let height = crate::entities::common::parse_length(text)?;
|
let height = crate::entities::common::parse_length(text)?;
|
||||||
if !height.is_finite() || height.abs() <= 1e-9 {
|
if !height.is_finite() || height <= 1e-9 {
|
||||||
return Some(false);
|
return Some(false);
|
||||||
}
|
}
|
||||||
let direction = glam::DVec3::new(
|
let direction = glam::DVec3::new(
|
||||||
@@ -1087,12 +1110,7 @@ fn apply_extrusion_geometry_property(
|
|||||||
let Some(unit) = direction.try_normalize() else {
|
let Some(unit) = direction.try_normalize() else {
|
||||||
return Some(false);
|
return Some(false);
|
||||||
};
|
};
|
||||||
let sign = if height.signum() == value.end_draft_distance.signum() {
|
let updated = unit * height;
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
-1.0
|
|
||||||
};
|
|
||||||
let updated = unit * height.abs() * sign;
|
|
||||||
if (updated - direction).length_squared() <= 1e-24 {
|
if (updated - direction).length_squared() <= 1e-24 {
|
||||||
return Some(false);
|
return Some(false);
|
||||||
}
|
}
|
||||||
|
|||||||
+113
-22
@@ -3,7 +3,7 @@
|
|||||||
use cadkernel::brep::{self, Body};
|
use cadkernel::brep::{self, Body};
|
||||||
use cadkernel::geom2d::{Arc, Curve, EllipseArc, Line};
|
use cadkernel::geom2d::{Arc, Curve, EllipseArc, Line};
|
||||||
use cadkernel::space::{PlanarCurve, Plane, Vec3};
|
use cadkernel::space::{PlanarCurve, Plane, Vec3};
|
||||||
use acadrust::entities::{EmbeddedEntity, LwPolyline, LwVertex};
|
use acadrust::entities::{EmbeddedEntity, LwPolyline, LwVertex, Spline};
|
||||||
use acadrust::objects::{
|
use acadrust::objects::{
|
||||||
SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep,
|
SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep,
|
||||||
};
|
};
|
||||||
@@ -229,22 +229,75 @@ pub fn extruded_surface(
|
|||||||
taper_angle: f64,
|
taper_angle: f64,
|
||||||
) -> Option<Body> {
|
) -> Option<Body> {
|
||||||
let (profile, closed) = extrusion_profile_of(entity)?;
|
let (profile, closed) = extrusion_profile_of(entity)?;
|
||||||
if closed {
|
let _ = closed;
|
||||||
brep::extrude_surface_tapered(
|
brep::extrude_surface_tapered(
|
||||||
profile.plane,
|
profile.plane,
|
||||||
&profile.pieces,
|
&profile.pieces,
|
||||||
direction,
|
direction,
|
||||||
taper_angle,
|
taper_angle,
|
||||||
)
|
)
|
||||||
} else if taper_angle.abs() <= 1e-12 {
|
|
||||||
brep::extrude_surface(profile.plane, &profile.pieces, direction)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extruded_along_path(entity: &EntityType, path: &EntityType) -> Option<Body> {
|
pub fn extruded_along_path(
|
||||||
swept(entity, path)
|
entity: &EntityType,
|
||||||
|
path: &EntityType,
|
||||||
|
taper_angle: f64,
|
||||||
|
) -> Option<Body> {
|
||||||
|
let profile = profile_of(entity)?;
|
||||||
|
if let EntityType::Polyline3D(path) = path {
|
||||||
|
let points = path
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.map(|vertex| [vertex.position.x, vertex.position.y, vertex.position.z])
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if taper_angle.abs() > 1e-12 {
|
||||||
|
if points.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return brep::extrude_tapered(
|
||||||
|
profile.plane,
|
||||||
|
&profile.pieces,
|
||||||
|
(Vec3::from(points[1]) - Vec3::from(points[0])).to_array(),
|
||||||
|
taper_angle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return brep::sweep_along_polyline3d(profile.plane, &profile.pieces, &points);
|
||||||
|
}
|
||||||
|
if taper_angle.abs() <= 1e-12 {
|
||||||
|
return swept(entity, path);
|
||||||
|
}
|
||||||
|
let curve = entity_curve(path)?;
|
||||||
|
let Curve::Line(line) = curve.curve else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let direction = Vec3::from(curve.plane.point_at(line.end))
|
||||||
|
- Vec3::from(curve.plane.point_at(line.start));
|
||||||
|
brep::extrude_tapered(
|
||||||
|
profile.plane,
|
||||||
|
&profile.pieces,
|
||||||
|
direction.to_array(),
|
||||||
|
taper_angle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn straight_path_direction(path: &EntityType) -> Option<[f64; 3]> {
|
||||||
|
if let EntityType::Polyline3D(path) = path {
|
||||||
|
if path.vertices.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let start = &path.vertices[0].position;
|
||||||
|
let end = &path.vertices[1].position;
|
||||||
|
return Some([end.x - start.x, end.y - start.y, end.z - start.z]);
|
||||||
|
}
|
||||||
|
let curve = entity_curve(path)?;
|
||||||
|
let Curve::Line(line) = curve.curve else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(
|
||||||
|
(Vec3::from(curve.plane.point_at(line.end))
|
||||||
|
- Vec3::from(curve.plane.point_at(line.start)))
|
||||||
|
.to_array(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signed distance of a drag along a profile's normal.
|
/// Signed distance of a drag along a profile's normal.
|
||||||
@@ -346,13 +399,28 @@ pub fn embedded_path(entity: &EntityType) -> Option<EmbeddedEntity> {
|
|||||||
EntityType::Ellipse(value) => Some(EmbeddedEntity::Ellipse(value.clone())),
|
EntityType::Ellipse(value) => Some(EmbeddedEntity::Ellipse(value.clone())),
|
||||||
EntityType::LwPolyline(value) => Some(EmbeddedEntity::LwPolyline(value.clone())),
|
EntityType::LwPolyline(value) => Some(EmbeddedEntity::LwPolyline(value.clone())),
|
||||||
EntityType::Spline(value) => Some(EmbeddedEntity::Spline(value.clone())),
|
EntityType::Spline(value) => Some(EmbeddedEntity::Spline(value.clone())),
|
||||||
|
EntityType::Polyline3D(value) if !value.is_closed() && value.vertices.len() >= 2 => {
|
||||||
|
let mut spline = Spline::from_control_points(
|
||||||
|
1,
|
||||||
|
value
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.map(|vertex| vertex.position)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
spline.flags.linear = true;
|
||||||
|
spline.flags.planar = false;
|
||||||
|
Some(EmbeddedEntity::Spline(spline))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn embedded_planar_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f64; 16])> {
|
fn embedded_planar_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f64; 16])> {
|
||||||
if let Some(embedded) = embedded_path(entity) {
|
if !matches!(entity, EntityType::Polyline3D(_)) {
|
||||||
return Some((embedded, glam::DMat4::IDENTITY.to_cols_array()));
|
if let Some(embedded) = embedded_path(entity) {
|
||||||
|
return Some((embedded, glam::DMat4::IDENTITY.to_cols_array()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let (profile, closed) = extrusion_profile_of(entity)?;
|
let (profile, closed) = extrusion_profile_of(entity)?;
|
||||||
let mut polyline = LwPolyline::new();
|
let mut polyline = LwPolyline::new();
|
||||||
@@ -406,11 +474,7 @@ pub fn extrusion_history(
|
|||||||
taper_angle: f64,
|
taper_angle: f64,
|
||||||
reference_point: [f64; 3],
|
reference_point: [f64; 3],
|
||||||
) -> Option<SolidHistoryOperation> {
|
) -> Option<SolidHistoryOperation> {
|
||||||
let signed_height = extrusion_profile_of(profile)
|
let signed_height = Vec3::from(direction).length();
|
||||||
.and_then(|(profile, _)| profile.plane.normal())
|
|
||||||
.map(|normal| Vec3::from(direction).dot(Vec3::from(normal)))
|
|
||||||
.filter(|value| value.is_finite())
|
|
||||||
.unwrap_or_else(|| Vec3::from(direction).length());
|
|
||||||
let mut base = SolidHistoryNodeBase::new(1);
|
let mut base = SolidHistoryNodeBase::new(1);
|
||||||
base.transform = glam::DMat4::IDENTITY.to_cols_array();
|
base.transform = glam::DMat4::IDENTITY.to_cols_array();
|
||||||
let (sweep_entity, sweep_entity_transform) = embedded_planar_entity(profile)?;
|
let (sweep_entity, sweep_entity_transform) = embedded_planar_entity(profile)?;
|
||||||
@@ -442,6 +506,33 @@ pub fn extrusion_history(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sweep_history(
|
||||||
|
profile: &EntityType,
|
||||||
|
path: &EntityType,
|
||||||
|
taper_angle: f64,
|
||||||
|
reference_point: [f64; 3],
|
||||||
|
) -> Option<SolidHistoryOperation> {
|
||||||
|
let mut base = SolidHistoryNodeBase::new(1);
|
||||||
|
base.transform = glam::DMat4::IDENTITY.to_cols_array();
|
||||||
|
let (sweep_entity, sweep_entity_transform) = embedded_planar_entity(profile)?;
|
||||||
|
Some(SolidHistoryOperation::Sweep(SolidHistorySweep {
|
||||||
|
base,
|
||||||
|
operation_major: 1,
|
||||||
|
sweep_entity: Some(sweep_entity),
|
||||||
|
path_entity: Some(embedded_path(path)?),
|
||||||
|
draft_angle: taper_angle,
|
||||||
|
scale_factor: 1.0,
|
||||||
|
sweep_entity_transform,
|
||||||
|
path_entity_transform: glam::DMat4::IDENTITY.to_cols_array(),
|
||||||
|
reference_point: Vector3::new(
|
||||||
|
reference_point[0],
|
||||||
|
reference_point[1],
|
||||||
|
reference_point[2],
|
||||||
|
),
|
||||||
|
..SolidHistorySweep::default()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a history-backed wall solid along a supported planar curve.
|
/// Builds a history-backed wall solid along a supported planar curve.
|
||||||
pub fn polysolid(
|
pub fn polysolid(
|
||||||
entity: &EntityType,
|
entity: &EntityType,
|
||||||
|
|||||||
Reference in new issue
Block a user