forked from 1b-agent/opencadstudio-upstream
Complete SWEEP command options and kernel integration
This commit is contained in:
1 parent
44c94d1da1
commit
ad47b17603
13 files changed
+709
-116
No files matched your search
Generated
+3
-3
@@ -71,8 +71,8 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.5.1"
|
||||
source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=393d6c0857447f9ca678bed97a612707f0a842c6#393d6c0857447f9ca678bed97a612707f0a842c6"
|
||||
version = "0.5.3"
|
||||
source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=305e93d4cf8ff70ce8449015edb201c2765fcc04#305e93d4cf8ff70ce8449015edb201c2765fcc04"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
@@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=1a1505c7bb8e26770f14b49859587660c7519a25#1a1505c7bb8e26770f14b49859587660c7519a25"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=209df414c9c0434af00edabc5529c6c774091f0e#209df414c9c0434af00edabc5529c6c774091f0e"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
|
||||
+3
-3
@@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
||||
rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "393d6c0857447f9ca678bed97a612707f0a842c6", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "1a1505c7bb8e26770f14b49859587660c7519a25", features = ["acis", "offset"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "305e93d4cf8ff70ce8449015edb201c2765fcc04", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "209df414c9c0434af00edabc5529c6c774091f0e", features = ["acis", "offset"] }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
@@ -61,7 +61,7 @@ iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aa
|
||||
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||
|
||||
[patch."https://github.com/HakanSeven12/cadcodec.git"]
|
||||
acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "393d6c0857447f9ca678bed97a612707f0a842c6" }
|
||||
acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "305e93d4cf8ff70ce8449015edb201c2765fcc04" }
|
||||
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
||||
+87
-38
@@ -353,6 +353,17 @@ impl OpenCADStudio {
|
||||
self.reset_tracking_after_point();
|
||||
self.push_ucs_to_cmd(i);
|
||||
}
|
||||
if let StepInput::EntityPick(handle, _) = &input {
|
||||
if self.tabs[i].active_cmd.as_ref()
|
||||
.is_some_and(|command| command.inject_before_entity_pick())
|
||||
{
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity(*handle).cloned() {
|
||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||
command.inject_picked_entity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let StepInput::SelectionComplete(handles) = &input {
|
||||
let entities = {
|
||||
let scene = &self.tabs[i].scene;
|
||||
@@ -3979,57 +3990,95 @@ impl OpenCADStudio {
|
||||
self.refresh_properties();
|
||||
}
|
||||
// ── SWEEP ─────────────────────────────────────────────────────
|
||||
CmdResult::SweepEntity {
|
||||
profile_handle,
|
||||
CmdResult::SweepEntities {
|
||||
handles,
|
||||
path_handle,
|
||||
mode,
|
||||
options,
|
||||
color: _,
|
||||
} => {
|
||||
if self.reject_locked_edit(i, profile_handle)
|
||||
|| self.reject_locked_edit(i, path_handle)
|
||||
{
|
||||
self.tabs[i].active_cmd = None;
|
||||
return Task::none();
|
||||
}
|
||||
use crate::command::ExtrudeMode;
|
||||
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||
use crate::scene::model::{solid_history, sweep_model};
|
||||
|
||||
let profile_ent = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(profile_handle)
|
||||
.cloned();
|
||||
let path_ent = self.tabs[i].scene.document.get_entity(path_handle).cloned();
|
||||
let result = profile_ent.zip(path_ent).and_then(|(profile, path)| {
|
||||
let reference_point = sweep_model::profile_of(&profile)?.plane.origin;
|
||||
let solid = sweep_model::swept(&profile, &path)?;
|
||||
let history = sweep_model::sweep_history(
|
||||
&profile,
|
||||
&path,
|
||||
0.0,
|
||||
reference_point,
|
||||
)
|
||||
.unwrap_or_else(|| solid_history::brep_op(&solid));
|
||||
Some((solid, history))
|
||||
});
|
||||
|
||||
if let Some((solid, history)) = result {
|
||||
let pending = self.begin_undo(i, "SWEEP", 1, true);
|
||||
let created = self.add_solid_model(empty_solid3d(), solid, history);
|
||||
if !created.is_null() {
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::t!("SWEEP: solid created.").as_ref());
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
use crate::scene::model::sweep_model;
|
||||
let delete_sources = self.tabs[i].scene.document.header.delete_objects;
|
||||
let path = self.tabs[i].scene.document.get_entity(path_handle).cloned();
|
||||
let mut profiles = Vec::new();
|
||||
let mut failed = 0usize;
|
||||
for handle in handles {
|
||||
if handle == path_handle || self.reject_locked_edit(i, handle) {
|
||||
failed += 1;
|
||||
continue;
|
||||
}
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity(handle).cloned() {
|
||||
profiles.push((handle, entity));
|
||||
} else {
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
let selection = profiles.iter().map(|(_, entity)| entity.clone()).collect::<Vec<_>>();
|
||||
let options = sweep_model::sweep_selection_options(&selection, options);
|
||||
let pending = if profiles.is_empty() {
|
||||
None
|
||||
} else {
|
||||
self.begin_undo(i, "SWEEP", profiles.len(), true)
|
||||
};
|
||||
let mut created_handles = Vec::new();
|
||||
let mut consumed = Vec::new();
|
||||
for (handle, profile) in profiles {
|
||||
let result = path.as_ref().zip(options).and_then(|(path, options)| {
|
||||
let record = sweep_model::sweep_record(&profile, path, options)?;
|
||||
let (_, _, closed) = cadkernel::acis::sweep_profile_geometry(
|
||||
record.sweep_entity.as_ref()?, record.sweep_entity_transform,
|
||||
).ok()?;
|
||||
let surface = mode == ExtrudeMode::Surface || !closed;
|
||||
let body = cadkernel::acis::rebuild_sweep_with_mode(&record, surface).ok()?;
|
||||
Some((body, record, surface))
|
||||
});
|
||||
let Some((body, record, surface)) = result else {
|
||||
failed += 1;
|
||||
continue;
|
||||
};
|
||||
let created = if surface {
|
||||
self.add_surface_model(sweep_model::swept_surface_entity(&record), body)
|
||||
} else {
|
||||
self.add_solid_model(
|
||||
empty_solid3d(), body,
|
||||
acadrust::objects::SolidHistoryOperation::Sweep(record),
|
||||
)
|
||||
};
|
||||
if created.is_null() {
|
||||
failed += 1;
|
||||
} else {
|
||||
created_handles.push(created);
|
||||
if delete_sources {
|
||||
consumed.push(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !created_handles.is_empty() {
|
||||
consumed.sort_unstable_by_key(|handle| handle.value());
|
||||
consumed.dedup();
|
||||
self.tabs[i].scene.erase_entities(&consumed);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
for handle in &created_handles {
|
||||
self.tabs[i].scene.select_entity(*handle, true);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"SWEEP: created %{created} object(s); %{failed} source(s) could not be swept.",
|
||||
created = created_handles.len(), failed = failed
|
||||
).as_ref());
|
||||
} else {
|
||||
self.command_line.push_error(crate::t!("SWEEP: could not sweep the profile along the path.").as_ref());
|
||||
}
|
||||
if let Some(pending) = pending {
|
||||
self.commit_undo_delta(i, pending);
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.restore_pre_cmd_tangent();
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
// ── LOFT ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -814,7 +814,16 @@ impl OpenCADStudio {
|
||||
"SWEEP" => {
|
||||
use crate::modules::insert::solid3d_cmds::SweepCommand;
|
||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||
let cmd = SweepCommand::new(color);
|
||||
let isolines = self.tabs[i].scene.document.header.isolines.max(0) as usize;
|
||||
let mut cmd = SweepCommand::new(color, isolines);
|
||||
let selected = self.tabs[i].scene.selected_handles_in_order()
|
||||
.into_iter()
|
||||
.filter_map(|handle| self.tabs[i].scene.document.get_entity(handle)
|
||||
.cloned().map(|entity| (handle, entity)))
|
||||
.collect::<Vec<_>>();
|
||||
if !selected.is_empty() {
|
||||
cmd.set_preselection(selected);
|
||||
}
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
|
||||
@@ -2298,6 +2298,15 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.set_hover_highlight(None);
|
||||
}
|
||||
let hover_handle = hovered.unwrap_or(acadrust::Handle::NULL);
|
||||
let wants_entity = self.tabs[i].active_cmd.as_ref()
|
||||
.is_some_and(|command| command.wants_hover_entity(hover_handle));
|
||||
if wants_entity {
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity(hover_handle).cloned() {
|
||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||
command.inject_hover_entity(hover_handle, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
let shift = self.shift_down;
|
||||
let mut p = self.tabs[i]
|
||||
.active_cmd
|
||||
|
||||
+36
-3
@@ -1142,6 +1142,29 @@ pub enum ExtrudeExtent {
|
||||
Path(Handle),
|
||||
}
|
||||
|
||||
/// Construction options shared by SWEEP creation and its live preview.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SweepOptions {
|
||||
pub align: bool,
|
||||
pub bank: bool,
|
||||
pub base_point: Option<DVec3>,
|
||||
pub scale: f64,
|
||||
/// Total twist along the path, in radians.
|
||||
pub twist_angle: f64,
|
||||
}
|
||||
|
||||
impl Default for SweepOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
align: true,
|
||||
bank: false,
|
||||
base_point: None,
|
||||
scale: 1.0,
|
||||
twist_angle: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned by every `CadCommand` method to tell main.rs what to do.
|
||||
#[allow(dead_code)]
|
||||
pub enum CmdResult {
|
||||
@@ -1453,10 +1476,12 @@ pub enum CmdResult {
|
||||
mode: ExtrudeMode,
|
||||
color: [f32; 4],
|
||||
},
|
||||
/// Sweep the profile entity `profile_handle` along `path_handle`.
|
||||
SweepEntity {
|
||||
profile_handle: Handle,
|
||||
/// Sweep the selected profiles along one path in a single undo step.
|
||||
SweepEntities {
|
||||
handles: Vec<Handle>,
|
||||
path_handle: Handle,
|
||||
mode: ExtrudeMode,
|
||||
options: SweepOptions,
|
||||
color: [f32; 4],
|
||||
},
|
||||
/// Loft through a series of profile entities.
|
||||
@@ -1951,6 +1976,14 @@ pub trait CadCommand: Send {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Request a source snapshot only when the hovered entity changes.
|
||||
/// Commands can cache geometry across mouse moves without holding a scene.
|
||||
fn wants_hover_entity(&self, _handle: Handle) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn inject_hover_entity(&mut self, _handle: Handle, _entity: EntityType) {}
|
||||
|
||||
/// Called on every mouse-move in the viewport.
|
||||
/// Return `Some(WireModel)` to update the rubber-band preview, `None` to skip.
|
||||
fn on_mouse_move(&mut self, _pt: DVec3) -> Option<WireModel> {
|
||||
|
||||
@@ -204,6 +204,7 @@ fn embedded_name(value: Option<&acadrust::entities::EmbeddedEntity>) -> &'static
|
||||
Some(acadrust::entities::EmbeddedEntity::Ellipse(_)) => "Ellipse",
|
||||
Some(acadrust::entities::EmbeddedEntity::Spline(_)) => "Spline",
|
||||
Some(acadrust::entities::EmbeddedEntity::LwPolyline(_)) => "Polyline",
|
||||
Some(acadrust::entities::EmbeddedEntity::Region(_)) => "Region",
|
||||
Some(acadrust::entities::EmbeddedEntity::Ray(_)) => "Ray",
|
||||
Some(acadrust::entities::EmbeddedEntity::XLine(_)) => "XLine",
|
||||
Some(acadrust::entities::EmbeddedEntity::Unknown { .. }) => "Unknown",
|
||||
|
||||
@@ -371,6 +371,7 @@ fn embedded_name(entity: Option<&acadrust::entities::EmbeddedEntity>) -> &'stati
|
||||
Some(acadrust::entities::EmbeddedEntity::Ellipse(_)) => "Ellipse",
|
||||
Some(acadrust::entities::EmbeddedEntity::Spline(_)) => "Spline",
|
||||
Some(acadrust::entities::EmbeddedEntity::LwPolyline(_)) => "Polyline",
|
||||
Some(acadrust::entities::EmbeddedEntity::Region(_)) => "Region",
|
||||
Some(acadrust::entities::EmbeddedEntity::Ray(_)) => "Ray",
|
||||
Some(acadrust::entities::EmbeddedEntity::XLine(_)) => "XLine",
|
||||
Some(acadrust::entities::EmbeddedEntity::Unknown { .. }) => "Unknown",
|
||||
|
||||
@@ -6,7 +6,8 @@ use acadrust::{entities::Solid3D, EntityType, Handle};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{
|
||||
CadCommand, CmdOption, CmdResult, ExtrudeExtent, ExtrudeMode, SelectionEntity, WorkingPlane,
|
||||
CadCommand, CmdOption, CmdResult, ExtrudeExtent, ExtrudeMode, SelectionEntity, SweepOptions,
|
||||
WorkingPlane,
|
||||
};
|
||||
use crate::scene::WireModel;
|
||||
use crate::t;
|
||||
@@ -1184,24 +1185,118 @@ impl CadCommand for RevolveCommand {
|
||||
|
||||
pub struct SweepCommand {
|
||||
step: SweepStep,
|
||||
profile_handle: acadrust::Handle,
|
||||
profiles: Vec<(Handle, EntityType)>,
|
||||
injected_path: Option<EntityType>,
|
||||
hover_path: Option<(Handle, EntityType)>,
|
||||
preview_key: Option<(Handle, ExtrudeMode, SweepOptions)>,
|
||||
preview_cache: Vec<WireModel>,
|
||||
mode: ExtrudeMode,
|
||||
options: SweepOptions,
|
||||
reference_start: Option<DVec3>,
|
||||
reference_length: f64,
|
||||
new_length_start: Option<DVec3>,
|
||||
isolines: usize,
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum SweepStep {
|
||||
PickProfile,
|
||||
PickProfiles,
|
||||
Mode,
|
||||
PickPath,
|
||||
Alignment,
|
||||
BasePoint,
|
||||
Scale,
|
||||
ReferenceLength,
|
||||
ReferenceEnd,
|
||||
NewLength,
|
||||
NewLengthStart,
|
||||
NewLengthEnd,
|
||||
Twist,
|
||||
}
|
||||
|
||||
impl SweepCommand {
|
||||
pub fn new(color: [f32; 4]) -> Self {
|
||||
pub fn new(color: [f32; 4], isolines: usize) -> Self {
|
||||
Self {
|
||||
step: SweepStep::PickProfile,
|
||||
profile_handle: acadrust::Handle::NULL,
|
||||
step: SweepStep::PickProfiles,
|
||||
profiles: Vec::new(),
|
||||
injected_path: None,
|
||||
hover_path: None,
|
||||
preview_key: None,
|
||||
preview_cache: Vec::new(),
|
||||
mode: ExtrudeMode::Solid,
|
||||
options: SweepOptions::default(),
|
||||
reference_start: None,
|
||||
reference_length: 1.0,
|
||||
new_length_start: None,
|
||||
isolines,
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_preselection(&mut self, profiles: Vec<(Handle, EntityType)>) {
|
||||
self.set_profiles(profiles);
|
||||
if !self.profiles.is_empty() {
|
||||
self.step = SweepStep::PickPath;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_profiles(&mut self, profiles: Vec<(Handle, EntityType)>) {
|
||||
self.profiles.clear();
|
||||
for (handle, entity) in profiles {
|
||||
if !handle.is_null()
|
||||
&& !self.profiles.iter().any(|(selected, _)| *selected == handle)
|
||||
&& crate::scene::model::sweep_model::is_sweep_profile(&entity)
|
||||
{
|
||||
self.profiles.push((handle, entity));
|
||||
}
|
||||
}
|
||||
self.preview_key = None;
|
||||
self.preview_cache.clear();
|
||||
}
|
||||
|
||||
fn contains_profile(&self, handle: Handle) -> bool {
|
||||
self.profiles.iter().any(|(selected, _)| *selected == handle)
|
||||
}
|
||||
|
||||
fn selection_options(&self) -> Option<SweepOptions> {
|
||||
let profiles = self.profiles.iter().map(|(_, entity)| entity.clone()).collect::<Vec<_>>();
|
||||
crate::scene::model::sweep_model::sweep_selection_options(&profiles, self.options)
|
||||
}
|
||||
|
||||
fn finish(&self, path_handle: Handle) -> CmdResult {
|
||||
CmdResult::SweepEntities {
|
||||
handles: self.profiles.iter().map(|(handle, _)| *handle).collect(),
|
||||
path_handle,
|
||||
mode: self.mode,
|
||||
options: self.options,
|
||||
color: self.color,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_scale(&mut self, scale: f64) -> bool {
|
||||
if !scale.is_finite() || scale <= 0.0 {
|
||||
return false;
|
||||
}
|
||||
self.options.scale = scale;
|
||||
self.step = SweepStep::PickPath;
|
||||
true
|
||||
}
|
||||
|
||||
fn set_reference_length(&mut self, length: f64) -> bool {
|
||||
if !length.is_finite() || length <= 1e-12 {
|
||||
return false;
|
||||
}
|
||||
self.reference_length = length;
|
||||
self.step = SweepStep::NewLength;
|
||||
true
|
||||
}
|
||||
|
||||
fn length_anchor(&self) -> DVec3 {
|
||||
self.reference_start
|
||||
.or_else(|| self.selection_options().and_then(|options| options.base_point))
|
||||
.unwrap_or(DVec3::ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for SweepCommand {
|
||||
@@ -1210,37 +1305,301 @@ impl CadCommand for SweepCommand {
|
||||
}
|
||||
fn prompt(&self) -> String {
|
||||
match self.step {
|
||||
SweepStep::PickProfile => t!("SWEEP Select profile to sweep:").into_owned(),
|
||||
SweepStep::PickPath => {
|
||||
t!("SWEEP Select path (Line, Arc, LwPolyline):").into_owned()
|
||||
}
|
||||
SweepStep::PickProfiles => t!("SWEEP Select objects to sweep or [Mode] (Enter to finish):").into_owned(),
|
||||
SweepStep::Mode => format!(
|
||||
"{} <{}>:",
|
||||
t!("SWEEP Creation mode [Solid/Surface]"),
|
||||
if self.mode == ExtrudeMode::Solid { "Solid" } else { "Surface" },
|
||||
),
|
||||
SweepStep::PickPath => t!("SWEEP Select sweep path or [Alignment/Base point/Scale/Twist]:").into_owned(),
|
||||
SweepStep::Alignment => format!(
|
||||
"{} <{}>:",
|
||||
t!("SWEEP Align sweep object perpendicular to path [Yes/No]"),
|
||||
if self.options.align { "Yes" } else { "No" },
|
||||
),
|
||||
SweepStep::BasePoint => t!("SWEEP Specify base point:").into_owned(),
|
||||
SweepStep::Scale => format!(
|
||||
"{} <{}>:", t!("SWEEP Enter scale factor or [Reference]"), self.options.scale,
|
||||
),
|
||||
SweepStep::ReferenceLength => format!(
|
||||
"{} <{}>:",
|
||||
t!("SWEEP Specify reference length or first point"),
|
||||
crate::entities::common::format_length(self.reference_length),
|
||||
),
|
||||
SweepStep::ReferenceEnd => t!("SWEEP Specify second reference point:").into_owned(),
|
||||
SweepStep::NewLength => format!(
|
||||
"{} <{}>:",
|
||||
t!("SWEEP Specify new length or [Points]"),
|
||||
crate::entities::common::format_length(self.reference_length * self.options.scale),
|
||||
),
|
||||
SweepStep::NewLengthStart => t!("SWEEP Specify first point of new length:").into_owned(),
|
||||
SweepStep::NewLengthEnd => t!("SWEEP Specify second point of new length:").into_owned(),
|
||||
SweepStep::Twist => format!(
|
||||
"{} <{}>:",
|
||||
t!("SWEEP Specify twist angle or [Bank]"),
|
||||
crate::entities::common::format_angle(self.options.twist_angle),
|
||||
),
|
||||
}
|
||||
}
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
true
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
match self.step {
|
||||
SweepStep::PickProfiles => vec![CmdOption::new("Mode", "MODE"), CmdOption::enter("Done")],
|
||||
SweepStep::Mode => vec![CmdOption::new("Solid", "SOLID"), CmdOption::new("Surface", "SURFACE")],
|
||||
SweepStep::PickPath => vec![
|
||||
CmdOption::new("Alignment", "ALIGNMENT"),
|
||||
CmdOption::new("Base point", "BASE"),
|
||||
CmdOption::new("Scale", "SCALE"),
|
||||
CmdOption::new("Twist", "TWIST"),
|
||||
],
|
||||
SweepStep::Alignment => vec![CmdOption::new("Yes", "YES"), CmdOption::new("No", "NO")],
|
||||
SweepStep::Scale => vec![CmdOption::new("Reference", "REFERENCE")],
|
||||
SweepStep::NewLength => vec![CmdOption::new("Points", "POINTS")],
|
||||
SweepStep::Twist => vec![CmdOption::new("Bank", "BANK")],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
fn on_entity_pick(&mut self, handle: acadrust::Handle, _pt: DVec3) -> CmdResult {
|
||||
if handle.is_null() {
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
self.step == SweepStep::PickPath
|
||||
}
|
||||
|
||||
fn entity_pick_highlights_hover(&self) -> bool {
|
||||
self.step == SweepStep::PickPath
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
|
||||
if self.step != SweepStep::PickPath || handle.is_null() || self.contains_profile(handle) {
|
||||
self.injected_path = None;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
let path = self.injected_path.take().or_else(|| {
|
||||
self.hover_path.as_ref()
|
||||
.filter(|(hovered, _)| *hovered == handle)
|
||||
.map(|(_, entity)| entity.clone())
|
||||
});
|
||||
if path.as_ref().is_some_and(crate::scene::model::sweep_model::is_sweep_path) {
|
||||
self.finish(handle)
|
||||
} else {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||
if !point.is_finite() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
match self.step {
|
||||
SweepStep::PickProfile => {
|
||||
self.profile_handle = handle;
|
||||
SweepStep::BasePoint => {
|
||||
self.options.base_point = Some(point);
|
||||
self.step = SweepStep::PickPath;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
SweepStep::PickPath => CmdResult::SweepEntity {
|
||||
profile_handle: self.profile_handle,
|
||||
path_handle: handle,
|
||||
color: self.color,
|
||||
},
|
||||
SweepStep::ReferenceLength => {
|
||||
self.reference_start = Some(point);
|
||||
self.step = SweepStep::ReferenceEnd;
|
||||
}
|
||||
SweepStep::ReferenceEnd => {
|
||||
if let Some(start) = self.reference_start {
|
||||
self.set_reference_length(point.distance(start));
|
||||
}
|
||||
}
|
||||
SweepStep::NewLength => {
|
||||
self.set_scale(point.distance(self.length_anchor()) / self.reference_length);
|
||||
}
|
||||
SweepStep::NewLengthStart => {
|
||||
self.new_length_start = Some(point);
|
||||
self.step = SweepStep::NewLengthEnd;
|
||||
}
|
||||
SweepStep::NewLengthEnd => {
|
||||
if let Some(start) = self.new_length_start {
|
||||
self.set_scale(point.distance(start) / self.reference_length);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
matches!(self.step,
|
||||
SweepStep::PickProfiles | SweepStep::Mode | SweepStep::PickPath
|
||||
| SweepStep::Alignment | SweepStep::Scale | SweepStep::ReferenceLength
|
||||
| SweepStep::NewLength | SweepStep::Twist
|
||||
)
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
matches!(self.step, SweepStep::PickPath | SweepStep::ReferenceLength | SweepStep::NewLength)
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let value = text.trim();
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let keyword = value.to_ascii_uppercase();
|
||||
match self.step {
|
||||
SweepStep::PickProfiles if "MODE".starts_with(&keyword) => {
|
||||
self.step = SweepStep::Mode;
|
||||
}
|
||||
SweepStep::Mode => {
|
||||
if "SOLID".starts_with(&keyword) {
|
||||
self.mode = ExtrudeMode::Solid;
|
||||
} else if "SURFACE".starts_with(&keyword) {
|
||||
self.mode = ExtrudeMode::Surface;
|
||||
} else {
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
self.step = SweepStep::PickProfiles;
|
||||
}
|
||||
SweepStep::PickPath => {
|
||||
self.step = if "ALIGNMENT".starts_with(&keyword) {
|
||||
SweepStep::Alignment
|
||||
} else if "BASE".starts_with(&keyword) || "BASE POINT".starts_with(&keyword) {
|
||||
SweepStep::BasePoint
|
||||
} else if "SCALE".starts_with(&keyword) {
|
||||
SweepStep::Scale
|
||||
} else if "TWIST".starts_with(&keyword) {
|
||||
SweepStep::Twist
|
||||
} else {
|
||||
// Unconsumed text can be an object handle from automation.
|
||||
return None;
|
||||
};
|
||||
}
|
||||
SweepStep::Alignment => {
|
||||
if "YES".starts_with(&keyword) {
|
||||
self.options.align = true;
|
||||
} else if "NO".starts_with(&keyword) {
|
||||
self.options.align = false;
|
||||
} else {
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
self.step = SweepStep::PickPath;
|
||||
}
|
||||
SweepStep::Scale => {
|
||||
if "REFERENCE".starts_with(&keyword) {
|
||||
self.reference_start = None;
|
||||
self.new_length_start = None;
|
||||
self.step = SweepStep::ReferenceLength;
|
||||
} else if let Ok(scale) = value.parse::<f64>() {
|
||||
self.set_scale(scale);
|
||||
}
|
||||
}
|
||||
SweepStep::ReferenceLength => {
|
||||
if let Some(length) = crate::entities::common::parse_typed_length(value) {
|
||||
self.set_reference_length(length);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
SweepStep::NewLength => {
|
||||
if "POINTS".starts_with(&keyword) {
|
||||
self.step = SweepStep::NewLengthStart;
|
||||
} else if let Some(length) = crate::entities::common::parse_typed_length(value) {
|
||||
self.set_scale(length / self.reference_length);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
SweepStep::Twist => {
|
||||
if "BANK".starts_with(&keyword) {
|
||||
self.options.bank = true;
|
||||
self.step = SweepStep::PickPath;
|
||||
} else if let Some(angle) = crate::entities::common::parse_angle(value) {
|
||||
if angle.is_finite() {
|
||||
self.options.twist_angle = angle;
|
||||
self.options.bank = false;
|
||||
self.step = SweepStep::PickPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
match self.step {
|
||||
SweepStep::PickProfiles if !self.profiles.is_empty() => {
|
||||
self.step = SweepStep::PickPath;
|
||||
}
|
||||
SweepStep::Mode => self.step = SweepStep::PickProfiles,
|
||||
SweepStep::Alignment | SweepStep::Scale | SweepStep::Twist
|
||||
| SweepStep::NewLength => self.step = SweepStep::PickPath,
|
||||
SweepStep::ReferenceLength => self.step = SweepStep::NewLength,
|
||||
_ => return CmdResult::Cancel,
|
||||
}
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn is_selection_gathering(&self) -> bool {
|
||||
self.step == SweepStep::PickProfiles
|
||||
}
|
||||
|
||||
fn selection_forces_add(&self) -> bool {
|
||||
self.step == SweepStep::PickProfiles
|
||||
}
|
||||
|
||||
fn inject_selection_entities(&mut self, entities: Vec<SelectionEntity>) {
|
||||
if self.step == SweepStep::PickProfiles {
|
||||
self.set_profiles(entities.into_iter().map(|entry| (entry.handle, entry.entity)).collect());
|
||||
}
|
||||
}
|
||||
|
||||
fn on_selection_complete(&mut self, _handles: Vec<Handle>) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn inject_before_entity_pick(&self) -> bool {
|
||||
self.step == SweepStep::PickPath
|
||||
}
|
||||
|
||||
fn inject_picked_entity(&mut self, entity: EntityType) {
|
||||
if self.step == SweepStep::PickPath {
|
||||
self.injected_path = Some(entity);
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_hover_entity(&self, handle: Handle) -> bool {
|
||||
self.step == SweepStep::PickPath
|
||||
&& !handle.is_null()
|
||||
&& !self.contains_profile(handle)
|
||||
&& self.hover_path.as_ref().is_none_or(|(hovered, _)| *hovered != handle)
|
||||
}
|
||||
|
||||
fn inject_hover_entity(&mut self, handle: Handle, entity: EntityType) {
|
||||
self.hover_path = Some((handle, entity));
|
||||
self.preview_key = None;
|
||||
}
|
||||
|
||||
fn on_hover_entity(&mut self, handle: Handle, _point: DVec3) -> Vec<WireModel> {
|
||||
if self.step != SweepStep::PickPath || handle.is_null() || self.contains_profile(handle) {
|
||||
return Vec::new();
|
||||
}
|
||||
let key = (handle, self.mode, self.options);
|
||||
if self.preview_key == Some(key) {
|
||||
return self.preview_cache.clone();
|
||||
}
|
||||
self.preview_cache.clear();
|
||||
let Some((hovered, path)) = self.hover_path.as_ref() else {
|
||||
return Vec::new();
|
||||
};
|
||||
if *hovered != handle {
|
||||
return Vec::new();
|
||||
}
|
||||
self.preview_key = Some(key);
|
||||
if !crate::scene::model::sweep_model::is_sweep_path(path) {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(options) = self.selection_options() else {
|
||||
return Vec::new();
|
||||
};
|
||||
self.preview_cache = self.profiles.iter().flat_map(|(_, profile)| {
|
||||
crate::scene::model::sweep_model::swept_with_options(profile, path, self.mode, options)
|
||||
.map(|body| preview_body_wires(&body, self.color, self.isolines))
|
||||
.unwrap_or_default()
|
||||
}).collect();
|
||||
self.preview_cache.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,5 +11,6 @@ pub mod mesh_model;
|
||||
pub mod solid_model;
|
||||
pub mod solid_history;
|
||||
pub mod sweep_model;
|
||||
mod sweep_command_model;
|
||||
pub mod visual_style_model;
|
||||
pub mod object;
|
||||
@@ -212,7 +212,9 @@ pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3>
|
||||
}
|
||||
SolidHistoryOperation::Cone(value) => world_point(value.base.transform, [0.0; 3]),
|
||||
SolidHistoryOperation::Cylinder(value) => world_point(value.base.transform, [0.0; 3]),
|
||||
SolidHistoryOperation::Sweep(value) | SolidHistoryOperation::Extrusion(value) => world_point(
|
||||
SolidHistoryOperation::Sweep(value) => cadkernel::acis::sweep_history_reference_point(value)
|
||||
.ok().map(glam::DVec3::from_array),
|
||||
SolidHistoryOperation::Extrusion(value) => world_point(
|
||||
value.base.transform,
|
||||
[
|
||||
value.reference_point.x,
|
||||
@@ -444,7 +446,7 @@ fn sweep_properties(
|
||||
let (show_history, show_history_editable) =
|
||||
displayed_history_state(object_show_history, show_history_mode);
|
||||
let show_value = if show_history { "Yes" } else { "No" };
|
||||
let length = sweep_path_length(value)
|
||||
let length = cadkernel::acis::sweep_history_path_length(value).ok()
|
||||
.map(crate::entities::common::format_length)
|
||||
.unwrap_or_default();
|
||||
vec![
|
||||
@@ -497,7 +499,7 @@ fn sweep_properties(
|
||||
field: PROP_HISTORY,
|
||||
value: PropValue::Choice {
|
||||
selected: if record_history { "Record" } else { "None" }.to_string(),
|
||||
options: vec!["None".to_string(), "Record".to_string()],
|
||||
options: vec!["Record".to_string(), "None".to_string()],
|
||||
},
|
||||
},
|
||||
Property {
|
||||
@@ -506,7 +508,7 @@ fn sweep_properties(
|
||||
value: if show_history_editable {
|
||||
PropValue::Choice {
|
||||
selected: show_value.to_string(),
|
||||
options: vec!["No".to_string(), "Yes".to_string()],
|
||||
options: vec!["Yes".to_string(), "No".to_string()],
|
||||
}
|
||||
} else {
|
||||
PropValue::ReadOnly(show_value.to_string())
|
||||
@@ -2179,6 +2181,7 @@ fn embedded_entity(value: &EmbeddedEntity) -> Option<EntityType> {
|
||||
EmbeddedEntity::Ellipse(value) => EntityType::Ellipse(value.clone()),
|
||||
EmbeddedEntity::Spline(value) => EntityType::Spline(value.clone()),
|
||||
EmbeddedEntity::LwPolyline(value) => EntityType::LwPolyline(value.clone()),
|
||||
EmbeddedEntity::Region(value) => EntityType::Region(value.clone()),
|
||||
EmbeddedEntity::Ray(value) => EntityType::Ray(value.clone()),
|
||||
EmbeddedEntity::XLine(value) => EntityType::XLine(value.clone()),
|
||||
EmbeddedEntity::Unknown { .. } => return None,
|
||||
@@ -2194,6 +2197,7 @@ fn into_embedded_entity(value: EntityType) -> Option<EmbeddedEntity> {
|
||||
EntityType::Ellipse(value) => EmbeddedEntity::Ellipse(value),
|
||||
EntityType::Spline(value) => EmbeddedEntity::Spline(value),
|
||||
EntityType::LwPolyline(value) => EmbeddedEntity::LwPolyline(value),
|
||||
EntityType::Region(value) => EmbeddedEntity::Region(value),
|
||||
EntityType::Ray(value) => EmbeddedEntity::Ray(value),
|
||||
EntityType::XLine(value) => EmbeddedEntity::XLine(value),
|
||||
_ => return None,
|
||||
@@ -2288,21 +2292,27 @@ fn apply_extrusion_profile_grip(
|
||||
true
|
||||
}
|
||||
|
||||
fn sweep_placement_matrices(value: &SolidHistorySweep) -> Option<(glam::DMat4, glam::DMat4)> {
|
||||
let (profile, path) = cadkernel::acis::sweep_history_placements(value).ok()?;
|
||||
let matrix = |place: cadkernel::brep::Placement| {
|
||||
glam::DMat4::from_cols(
|
||||
glam::DVec3::from_array(place.x_axis).extend(0.0),
|
||||
glam::DVec3::from_array(place.y_axis).extend(0.0),
|
||||
glam::DVec3::from_array(place.z_axis).extend(0.0),
|
||||
glam::DVec3::from_array(place.origin).extend(1.0),
|
||||
)
|
||||
};
|
||||
Some((matrix(profile), matrix(path)))
|
||||
}
|
||||
|
||||
fn sweep_embedded_grips(
|
||||
value: Option<&EmbeddedEntity>,
|
||||
base_transform: [f64; 16],
|
||||
entity_transform: [f64; 16],
|
||||
transform: glam::DMat4,
|
||||
first_id: usize,
|
||||
) -> Vec<GripDef> {
|
||||
let Some(entity) = value.and_then(embedded_entity) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let (Some(base), Some(entity_placement)) =
|
||||
(matrix(base_transform), matrix(entity_transform))
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let transform = base * entity_placement;
|
||||
entity
|
||||
.grips()
|
||||
.into_iter()
|
||||
@@ -2330,8 +2340,7 @@ fn sweep_embedded_grips(
|
||||
|
||||
fn apply_sweep_embedded_grip(
|
||||
value: &mut Option<EmbeddedEntity>,
|
||||
base_transform: [f64; 16],
|
||||
entity_transform: [f64; 16],
|
||||
transform: glam::DMat4,
|
||||
index: usize,
|
||||
apply: GripApply,
|
||||
) -> bool {
|
||||
@@ -2341,12 +2350,7 @@ fn apply_sweep_embedded_grip(
|
||||
let Some(source_grip) = entity.grips().get(index).cloned() else {
|
||||
return false;
|
||||
};
|
||||
let (Some(base), Some(entity_placement)) =
|
||||
(matrix(base_transform), matrix(entity_transform))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let inverse = (base * entity_placement).inverse();
|
||||
let inverse = transform.inverse();
|
||||
if !inverse.is_finite() {
|
||||
return false;
|
||||
}
|
||||
@@ -2891,18 +2895,14 @@ pub fn primitive_grips(
|
||||
);
|
||||
}
|
||||
SolidHistoryOperation::Sweep(value) => {
|
||||
grips.extend(sweep_embedded_grips(
|
||||
value.sweep_entity.as_ref(),
|
||||
value.base.transform,
|
||||
value.sweep_entity_transform,
|
||||
GRIP_SWEEP_PROFILE_FIRST,
|
||||
));
|
||||
grips.extend(sweep_embedded_grips(
|
||||
value.path_entity.as_ref(),
|
||||
value.base.transform,
|
||||
value.path_entity_transform,
|
||||
GRIP_SWEEP_PATH_FIRST,
|
||||
));
|
||||
if let Some((profile, path)) = sweep_placement_matrices(value) {
|
||||
grips.extend(sweep_embedded_grips(
|
||||
value.sweep_entity.as_ref(), profile, GRIP_SWEEP_PROFILE_FIRST,
|
||||
));
|
||||
grips.extend(sweep_embedded_grips(
|
||||
value.path_entity.as_ref(), path, GRIP_SWEEP_PATH_FIRST,
|
||||
));
|
||||
}
|
||||
}
|
||||
SolidHistoryOperation::Loft(value) => grips.extend(loft_section_grips(value)),
|
||||
SolidHistoryOperation::Revolve(value) => {
|
||||
@@ -2962,11 +2962,13 @@ pub fn apply_primitive_grip(
|
||||
}
|
||||
}
|
||||
if let SolidHistoryOperation::Sweep(value) = operation {
|
||||
let Some((profile, path)) = sweep_placement_matrices(value) else {
|
||||
return false;
|
||||
};
|
||||
if let Some(index) = grip_id.checked_sub(GRIP_SWEEP_PATH_FIRST) {
|
||||
return apply_sweep_embedded_grip(
|
||||
&mut value.path_entity,
|
||||
value.base.transform,
|
||||
value.path_entity_transform,
|
||||
path,
|
||||
index,
|
||||
apply,
|
||||
);
|
||||
@@ -2977,8 +2979,7 @@ pub fn apply_primitive_grip(
|
||||
{
|
||||
return apply_sweep_embedded_grip(
|
||||
&mut value.sweep_entity,
|
||||
value.base.transform,
|
||||
value.sweep_entity_transform,
|
||||
profile,
|
||||
index,
|
||||
apply,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//! SWEEP command data mapped to the shared kernel history reconstruction.
|
||||
|
||||
use acadrust::entities::{Surface, SurfaceData, SurfaceKind, SurfaceSweepOptions};
|
||||
use acadrust::objects::{SolidHistoryNodeBase, SolidHistorySweep};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use cadkernel::brep::Body;
|
||||
|
||||
use crate::command::{ExtrudeMode, SweepOptions};
|
||||
use super::sweep_model::{embedded_path, embedded_revolve_profile};
|
||||
|
||||
fn embedded_sweep_profile(entity: &EntityType) -> Option<(acadrust::entities::EmbeddedEntity, [f64; 16])> {
|
||||
if let EntityType::Region(region) = entity {
|
||||
Some((acadrust::entities::EmbeddedEntity::Region(region.clone()), glam::DMat4::IDENTITY.to_cols_array()))
|
||||
} else {
|
||||
embedded_revolve_profile(entity)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_sweep_profile(entity: &EntityType) -> bool {
|
||||
embedded_sweep_profile(entity).is_some_and(|(profile, transform)| {
|
||||
cadkernel::acis::sweep_profile_geometry(&profile, transform).is_ok()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_sweep_path(entity: &EntityType) -> bool {
|
||||
match embedded_path(entity) {
|
||||
Some(acadrust::entities::EmbeddedEntity::Spline(value)) => {
|
||||
value.degree > 0 && (value.control_points.len() > value.degree as usize
|
||||
|| value.fit_points.len() >= 2)
|
||||
}
|
||||
Some(_) => crate::entities::curve::entity_curve(entity)
|
||||
.is_some_and(|curve| curve.curve.length().is_finite() && curve.curve.length() > 1e-9),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// All selected profiles use one base point, preserving their relative offsets.
|
||||
pub fn sweep_selection_options(profiles: &[EntityType], mut options: SweepOptions) -> Option<SweepOptions> {
|
||||
if options.base_point.is_some() {
|
||||
return Some(options);
|
||||
}
|
||||
let geometry = profiles.iter().map(|profile| {
|
||||
let (entity, transform) = embedded_sweep_profile(profile)?;
|
||||
let (plane, wires, _) = cadkernel::acis::sweep_profile_geometry(&entity, transform).ok()?;
|
||||
Some((plane, wires))
|
||||
}).collect::<Option<Vec<_>>>()?;
|
||||
options.base_point = Some(glam::DVec3::from_array(
|
||||
cadkernel::brep::sweep_profile_group_base(&geometry)?,
|
||||
));
|
||||
Some(options)
|
||||
}
|
||||
|
||||
pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptions) -> Option<SolidHistorySweep> {
|
||||
let (sweep_entity, sweep_entity_transform) = embedded_sweep_profile(profile)?;
|
||||
let (plane, wires, _) = cadkernel::acis::sweep_profile_geometry(&sweep_entity, sweep_entity_transform).ok()?;
|
||||
let base_point = match options.base_point {
|
||||
Some(point) => point.to_array(),
|
||||
None => cadkernel::brep::sweep_profile_base(plane, &wires)?,
|
||||
};
|
||||
let mut base = SolidHistoryNodeBase::new(1);
|
||||
base.transform = glam::DMat4::IDENTITY.to_cols_array();
|
||||
Some(SolidHistorySweep {
|
||||
base,
|
||||
operation_major: 1,
|
||||
sweep_entity: Some(sweep_entity),
|
||||
path_entity: Some(embedded_path(path)?),
|
||||
scale_factor: options.scale,
|
||||
twist_angle: options.twist_angle,
|
||||
align_option: u8::from(options.align),
|
||||
has_align_start: true,
|
||||
bank: options.bank,
|
||||
sweep_entity_transform,
|
||||
path_entity_transform: glam::DMat4::IDENTITY.to_cols_array(),
|
||||
reference_point: Vector3::new(base_point[0], base_point[1], base_point[2]),
|
||||
..SolidHistorySweep::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn swept_with_options(profile: &EntityType, path: &EntityType, mode: ExtrudeMode, options: SweepOptions) -> Option<Body> {
|
||||
let record = sweep_record(profile, path, options)?;
|
||||
cadkernel::acis::rebuild_sweep_with_mode(&record, mode == ExtrudeMode::Surface).ok()
|
||||
}
|
||||
|
||||
/// Preserve native construction parameters alongside the sheet's saved B-rep.
|
||||
pub fn swept_surface_entity(record: &SolidHistorySweep) -> EntityType {
|
||||
let mut surface = Surface::new(SurfaceKind::Swept);
|
||||
if let Ok(point) = cadkernel::acis::sweep_history_reference_point(record) {
|
||||
surface.point_of_reference = Vector3::new(point[0], point[1], point[2]);
|
||||
}
|
||||
surface.surface_data = SurfaceData::Swept {
|
||||
class_version: 0,
|
||||
sweep_entity: record.sweep_entity.clone(),
|
||||
path_entity: record.path_entity.clone(),
|
||||
sweep_transform: glam::DMat4::IDENTITY.to_cols_array(),
|
||||
path_transform: glam::DMat4::IDENTITY.to_cols_array(),
|
||||
options: SurfaceSweepOptions {
|
||||
draft_angle: record.draft_angle,
|
||||
twist_angle: record.twist_angle,
|
||||
scale_factor: record.scale_factor,
|
||||
align_angle: record.align_angle,
|
||||
sweep_entity_transform: record.sweep_entity_transform,
|
||||
path_entity_transform: record.path_entity_transform,
|
||||
sweep_alignment_flags: record.align_option as i16,
|
||||
align_start: record.has_align_start,
|
||||
bank: record.bank,
|
||||
base_point_set: true,
|
||||
reference_vector: record.reference_point,
|
||||
..SurfaceSweepOptions::default()
|
||||
},
|
||||
};
|
||||
EntityType::Surface(surface)
|
||||
}
|
||||
@@ -12,6 +12,9 @@ use acadrust::types::{Vector2, Vector3};
|
||||
use acadrust::EntityType;
|
||||
|
||||
use crate::entities::curve::entity_curve;
|
||||
pub use super::sweep_command_model::{
|
||||
is_sweep_path, is_sweep_profile, sweep_record, sweep_selection_options, swept_surface_entity, swept_with_options,
|
||||
};
|
||||
|
||||
/// A drawn profile, as the kernel wants it: the plane it lies in and the
|
||||
/// chain of pieces closing a loop in that plane.
|
||||
@@ -591,18 +594,32 @@ pub fn embedded_path(entity: &EntityType) -> Option<EmbeddedEntity> {
|
||||
EntityType::Circle(value) => Some(EmbeddedEntity::Circle(value.clone())),
|
||||
EntityType::Ellipse(value) => Some(EmbeddedEntity::Ellipse(value.clone())),
|
||||
EntityType::LwPolyline(value) => Some(EmbeddedEntity::LwPolyline(value.clone())),
|
||||
EntityType::Polyline2D(value) => {
|
||||
let mut polyline = LwPolyline::new();
|
||||
polyline.elevation = value.elevation;
|
||||
polyline.normal = value.normal;
|
||||
polyline.is_closed = value.is_closed();
|
||||
polyline.vertices = value.vertices.iter().map(|vertex| {
|
||||
let mut converted = LwVertex::new(Vector2::new(vertex.location.x, vertex.location.y));
|
||||
converted.bulge = vertex.bulge;
|
||||
converted
|
||||
}).collect();
|
||||
Some(EmbeddedEntity::LwPolyline(polyline))
|
||||
}
|
||||
EntityType::Spline(value) => Some(EmbeddedEntity::Spline(value.clone())),
|
||||
EntityType::Polyline3D(value) if !value.is_closed() && value.vertices.len() >= 2 => {
|
||||
EntityType::Helix(value) => Some(EmbeddedEntity::Spline(value.spline.clone())),
|
||||
EntityType::Polyline3D(value) if value.vertices.len() >= 2 => {
|
||||
let mut points = value.vertices.iter().map(|vertex| vertex.position).collect::<Vec<_>>();
|
||||
if value.is_closed() && points.first() != points.last() {
|
||||
points.push(*points.first()?);
|
||||
}
|
||||
let mut spline = Spline::from_control_points(
|
||||
1,
|
||||
value
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| vertex.position)
|
||||
.collect(),
|
||||
points,
|
||||
);
|
||||
spline.flags.linear = true;
|
||||
spline.flags.planar = false;
|
||||
spline.flags.closed = value.is_closed();
|
||||
Some(EmbeddedEntity::Spline(spline))
|
||||
}
|
||||
_ => None,
|
||||
|
||||
Reference in new issue
Block a user