Restore loft section grip editing (#975)

Restore loft section grip editing
This commit is contained in:
Hakan Seven authored and GitHub committed 2026-09-02 17:56:27 +03:00
commit ba95c29167
5 files changed
+132 -6

No files matched your search

Generated
+1 -1
View File
@@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadkernel"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=5a4c287236e03a23be4d5099a45b16346e9f9442#5a4c287236e03a23be4d5099a45b16346e9f9442"
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=92578f0cc8e4eec0678b72c9bd1c701ff21b57b7#92578f0cc8e4eec0678b72c9bd1c701ff21b57b7"
dependencies = [
"acadrust",
"cavalier_contours",
+1 -1
View File
@@ -28,7 +28,7 @@ rfd = "0.17"
clap = { version = "4", features = ["derive"] }
env_logger = "0.11"
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "a0f7d444f1607bc4b2c881060cbe7ea1014253cb", features = ["serde"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "5a4c287236e03a23be4d5099a45b16346e9f9442", features = ["acis", "offset"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "92578f0cc8e4eec0678b72c9bd1c701ff21b57b7", features = ["acis", "offset"] }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
+13 -2
View File
@@ -3945,9 +3945,20 @@ impl OpenCADStudio {
.iter()
.filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned())
.collect();
if let Some(solid) = sweep_model::lofted(&profiles) {
let result = sweep_model::loft_history(&profiles)
.and_then(|history| {
cadkernel::acis::rebuild_body(&history)
.ok()
.map(|solid| (solid, history))
})
.or_else(|| {
sweep_model::lofted(&profiles).map(|solid| {
let history = solid_history::brep_op(&solid);
(solid, history)
})
});
if let Some((solid, history)) = result {
let pending = self.begin_undo(i, "LOFT", 1, true);
let history = solid_history::brep_op(&solid);
let created = self.add_solid_model(empty_solid3d(), solid, history);
if !created.is_null() {
self.tabs[i].dirty = true;
+96 -1
View File
@@ -1,7 +1,7 @@
use acadrust::entities::{EmbeddedEntity, Solid3D};
use acadrust::objects::{
DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone,
SolidHistoryCylinder, SolidHistoryNodeBase, SolidHistoryOperation,
SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation,
SolidHistoryPyramid, SolidHistorySphere, SolidHistorySweep, SolidHistoryTorus,
};
use acadrust::EntityType;
@@ -28,6 +28,7 @@ pub const GRIP_DRAFT: usize = 10_012;
pub const GRIP_PROFILE_FIRST: usize = 10_200;
pub const GRIP_SWEEP_PROFILE_FIRST: usize = 20_000;
pub const GRIP_SWEEP_PATH_FIRST: usize = 30_000;
pub const GRIP_LOFT_SECTION_FIRST: usize = 40_000;
pub const GRIP_BOX_CORNER_FIRST: usize = 10_100;
pub const GRIP_BOX_FACE_X_MIN: usize = 10_110;
pub const GRIP_BOX_FACE_X_MAX: usize = 10_111;
@@ -2057,6 +2058,94 @@ fn apply_sweep_embedded_grip(
true
}
fn loft_section_grips(value: &SolidHistoryLoft) -> Vec<GripDef> {
if !value.guides.is_empty() {
return Vec::new();
}
let Some(transform) = matrix(value.base.transform) else {
return Vec::new();
};
let mut result = Vec::new();
let mut index = 0;
for section in &value.cross_sections {
let Some(entity) = embedded_entity(section) else {
continue;
};
for source in entity.grips() {
let id = GRIP_LOFT_SECTION_FIRST + index;
index += 1;
let world = transform.transform_point3(source.world);
if !world.is_finite() {
continue;
}
let transform_vector = |vector: glam::DVec3| {
let vector = transform.transform_vector3(vector);
(vector.length_squared() > 1e-12).then(|| vector.normalize())
};
result.push(GripDef {
id,
world,
is_midpoint: source.is_midpoint,
shape: source.shape,
dir: source.dir.and_then(transform_vector),
axis: source.axis.and_then(transform_vector),
});
}
}
result
}
fn apply_loft_section_grip(
value: &mut SolidHistoryLoft,
mut index: usize,
apply: GripApply,
) -> bool {
if !value.guides.is_empty() {
return false;
}
let Some(transform) = matrix(value.base.transform) else {
return false;
};
let inverse = transform.inverse();
if !inverse.is_finite() {
return false;
}
let local_apply = match apply {
GripApply::Absolute(world) => {
let local = inverse.transform_point3(world);
if !local.is_finite() {
return false;
}
GripApply::Absolute(local)
}
GripApply::Translate(world_delta) => {
let local_delta = inverse.transform_vector3(world_delta);
if !local_delta.is_finite() || local_delta.length_squared() <= 1e-24 {
return false;
}
GripApply::Translate(local_delta)
}
};
for section in &mut value.cross_sections {
let Some(mut entity) = embedded_entity(section) else {
continue;
};
let grips = entity.grips();
if index >= grips.len() {
index -= grips.len();
continue;
}
let grip_id = grips[index].id;
entity.apply_grip(grip_id, local_apply);
let Some(entity) = into_embedded_entity(entity) else {
return false;
};
*section = entity;
return true;
}
false
}
fn extrusion_draft_grip(
value: &SolidHistorySweep,
) -> Option<(glam::DVec3, glam::DVec3, glam::DVec3, f64, f64)> {
@@ -2382,6 +2471,7 @@ pub fn primitive_grips(
GRIP_SWEEP_PATH_FIRST,
));
}
SolidHistoryOperation::Loft(value) => grips.extend(loft_section_grips(value)),
SolidHistoryOperation::Extrusion(value) => {
let (profile_grips, profile_center) = extrusion_profile_grips(value);
grips.extend(profile_grips);
@@ -2418,6 +2508,11 @@ pub fn apply_primitive_grip(
grip_id: usize,
apply: GripApply,
) -> bool {
if let SolidHistoryOperation::Loft(value) = operation {
if let Some(index) = grip_id.checked_sub(GRIP_LOFT_SECTION_FIRST) {
return apply_loft_section_grip(value, index, apply);
}
}
if let SolidHistoryOperation::Sweep(value) = operation {
if let Some(index) = grip_id.checked_sub(GRIP_SWEEP_PATH_FIRST) {
return apply_sweep_embedded_grip(
+21 -1
View File
@@ -5,7 +5,7 @@ use cadkernel::geom2d::{Arc, Curve, EllipseArc, Line};
use cadkernel::space::{PlanarCurve, Plane, Vec3};
use acadrust::entities::{EmbeddedEntity, LwPolyline, LwVertex, Spline};
use acadrust::objects::{
SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep,
SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep,
};
use acadrust::types::{Vector2, Vector3};
use acadrust::EntityType;
@@ -451,6 +451,26 @@ pub fn lofted(profiles: &[EntityType]) -> Option<Body> {
brep::loft(&sections)
}
/// Stores the source sections required to rebuild and edit a loft.
pub fn loft_history(profiles: &[EntityType]) -> Option<SolidHistoryOperation> {
let cross_sections = profiles
.iter()
.map(embedded_path)
.collect::<Option<Vec<_>>>()?;
if cross_sections.len() < 2 {
return None;
}
let mut base = SolidHistoryNodeBase::new(1);
base.transform = glam::DMat4::IDENTITY.to_cols_array();
Some(SolidHistoryOperation::Loft(SolidHistoryLoft {
base,
operation_major: 1,
cross_sections,
guides: Vec::new(),
..SolidHistoryLoft::default()
}))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PolysolidJustification {
Left,