Fix live extrusion surface grip editing

This commit is contained in:
ramox81 committed 2026-09-10 14:53:48 +03:00
1 parent c0b254950f
commit a24d1e6108
11 files changed
+121 -27

No files matched your search

Generated
+1 -1
View File
@@ -885,7 +885,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadkernel"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=dfd0de176a878c20a1aeb8d23b0b8265197b5f5c#dfd0de176a878c20a1aeb8d23b0b8265197b5f5c"
source = "git+https://github.com/ramox81/cadkernel.git?rev=c584d19dc45b3bef5146d6cf2e3dae79b72e9468#c584d19dc45b3bef5146d6cf2e3dae79b72e9468"
dependencies = [
"acadrust",
"cavalier_contours",
+1 -1
View File
@@ -32,7 +32,7 @@ rfd = "0.17"
clap = { version = "4", features = ["derive", "string"] }
env_logger = "0.11"
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "f23ffc873228e5f41b876485c741e4a8ac20a67d", features = ["serde"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "dfd0de176a878c20a1aeb8d23b0b8265197b5f5c", features = ["acis", "offset"] }
cadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "c584d19dc45b3bef5146d6cf2e3dae79b72e9468", features = ["acis", "offset"] }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
+1 -1
View File
@@ -15,7 +15,7 @@ thiserror = "1"
# Host adapters convert ObjectId to acadrust handles; kernel handles stay local.
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "f23ffc873228e5f41b876485c741e4a8ac20a67d", features = ["serde"], optional = true }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "dfd0de176a878c20a1aeb8d23b0b8265197b5f5c", features = ["acis", "offset"], optional = true }
cadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "c584d19dc45b3bef5146d6cf2e3dae79b72e9468", features = ["acis", "offset"], optional = true }
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
[build-dependencies]
+44 -9
View File
@@ -1535,15 +1535,30 @@ impl OpenCADStudio {
}
if let Some(axis) = grip.axis {
snapped = cursor_on_projected_axis(
p,
bounds,
view_rot,
eye,
grip.origin_world,
axis,
)
.unwrap_or(snapped);
let acquired_point = otrack_hit.is_some()
|| snap_hit.is_some_and(|hit| {
hit.snap_type != crate::snap::SnapType::Grid
});
if acquired_point {
if let Some(axis) = axis.try_normalize() {
// An axis grip still moves on its construction axis, but
// an acquired object point supplies the exact coordinate
// along that axis. This lets height and draft arrows seat
// back onto existing surface/solid geometry.
snapped = grip.origin_world
+ axis * (snapped - grip.origin_world).dot(axis);
}
} else {
snapped = cursor_on_projected_axis(
p,
bounds,
view_rot,
eye,
grip.origin_world,
axis,
)
.unwrap_or(snapped);
}
}
let snap_ms = snap_started.elapsed().as_secs_f64() * 1000.0;
@@ -1751,6 +1766,26 @@ impl OpenCADStudio {
let preview_ms = preview_started.elapsed().as_secs_f64() * 1000.0;
let geometry_ms = grip_started.elapsed().as_secs_f64() * 1000.0;
self.refresh_selected_grips();
// A history rebuild can clamp a requested value or move a derived
// construction handle non-linearly. Rebase each active target onto
// the freshly rebuilt grip so the hot arrow remains attached to the
// live surface and reverses immediately from a geometric limit.
let refreshed_targets: Vec<_> = self.tabs[i]
.selected_grip_handles
.iter()
.copied()
.zip(self.tabs[i].selected_grips.iter())
.map(|(handle, grip)| ((handle, grip.id), grip.world))
.collect();
if let Some(active) = self.tabs[i].active_grip.as_mut() {
for target in &mut active.targets {
if let Some((_, world)) = refreshed_targets.iter().find(|((handle, id), _)| {
*handle == target.handle && *id == target.grip_id
}) {
target.last_world = *world;
}
}
}
let grips_ms = grip_started.elapsed().as_secs_f64() * 1000.0 - geometry_ms;
// Properties are refreshed when the grip is committed or cancelled.
// Rebuilding the inspector on every pointer event adds no drawing
+1 -1
View File
@@ -210,7 +210,7 @@ impl SolidEdgeCommand {
)
.ok()?,
};
let mut wires = crate::scene::model::solid_model::grip_preview_wires(&result, handle);
let mut wires = crate::scene::model::solid_model::grip_preview_wires(&result, handle, 0);
for wire in &mut wires {
wire.color = self.preview_color;
wire.name = format!("{}-{}-PREVIEW", handle.value(), self.name());
+3 -1
View File
@@ -25,6 +25,7 @@ pub fn tessellate_sat(
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
planar_isolines: bool,
) -> Option<MeshLodSet> {
let (bodies, loss) = lift(document);
if bodies.is_empty() {
@@ -114,7 +115,8 @@ pub fn tessellate_sat(
max_angle,
source_fit * placement_scale,
)
.with_isolines(isolines);
.with_isolines(isolines)
.with_planar_isolines(planar_isolines);
if let Some(deflection) = chordal_deflection {
tolerance = tolerance.with_chordal_deflection(deflection);
}
+8
View File
@@ -11,6 +11,7 @@ fn tessellate_acis(
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
planar_isolines: bool,
) -> Option<MeshLodSet> {
crate::scene::convert::acis_kernel::tessellate_sat(
sat,
@@ -19,6 +20,7 @@ fn tessellate_acis(
facet_res,
chordal_deflection,
isolines,
planar_isolines,
)
}
@@ -219,6 +221,7 @@ fn finish(
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
planar_isolines: bool,
acis: &acadrust::entities::AcisData,
) -> Option<MeshLodSet> {
let mut set = tessellate_acis(
@@ -228,6 +231,7 @@ fn finish(
facet_res,
chordal_deflection,
isolines,
planar_isolines,
)?;
remap_acis_material_bindings(&mut set, acis);
Some(set)
@@ -252,6 +256,7 @@ pub fn tessellate_region(
facet_res,
chordal_deflection,
isolines,
false,
&region.acis_data,
)
}
@@ -275,6 +280,7 @@ pub fn tessellate_body(
facet_res,
chordal_deflection,
isolines,
false,
&body.acis_data,
)
}
@@ -298,6 +304,7 @@ pub fn tessellate_surface(
facet_res,
chordal_deflection,
isolines,
true,
&surface.acis_data,
)
}
@@ -321,6 +328,7 @@ pub fn tessellate_solid3d(
facet_res,
chordal_deflection,
isolines,
false,
&solid.acis_data,
)
}
+5
View File
@@ -3083,6 +3083,10 @@ impl Scene {
let chordal_deflection =
crate::entities::solid3d::display_deflection(&self.document.header, facet_resolution);
let isolines = self.document.header.isolines.max(0) as usize;
let planar_isolines = matches!(
self.document.get_entity(handle),
Some(EntityType::Surface(_))
);
let (mut set, wires, center) =
crate::scene::model::solid_model::display_from_solid(
solid,
@@ -3090,6 +3094,7 @@ impl Scene {
facet_resolution,
chordal_deflection,
isolines,
planar_isolines,
)?;
let name = handle.value().to_string();
for mesh in &mut set.lods {
+15 -4
View File
@@ -3537,21 +3537,32 @@ pub fn primitive_grips(
if let (Some(center), Some(base)) = (profile_center, matrix(value.base.transform)) {
let direction = base.transform_vector3(glam::DVec3::from_array(direction));
if direction.length_squared() > 1e-12 {
grips.push(grip(
let mut height_grip = grip(
GRIP_HEIGHT,
center + direction,
GripShape::Triangle,
Some(direction.normalize()),
));
);
if is_surface {
// Surface construction arrows keep one stable screen
// orientation while their world-space axis continues to
// constrain the edit.
height_grip.dir = None;
}
grips.push(height_grip);
}
}
if let Some((world, _, radial, _, _)) = extrusion_draft_grip(value) {
grips.push(grip(
let mut draft_grip = grip(
GRIP_DRAFT,
world,
GripShape::Triangle,
Some(radial),
));
);
if is_surface {
draft_grip.dir = None;
}
grips.push(draft_grip);
}
}
}
+33 -8
View File
@@ -23,6 +23,7 @@ fn display_tessellation(
facet_resolution: f64,
chordal_deflection: Option<f64>,
isolines: usize,
planar_isolines: bool,
) -> brep::mesh::BodyMesh {
let resolution = if facet_resolution.is_finite() && facet_resolution > 0.0 {
facet_resolution.clamp(0.01, 10.0)
@@ -34,7 +35,8 @@ fn display_tessellation(
|_| cadkernel::tessellation::display_angle_for_resolution(resolution),
);
let mut tolerance = brep::mesh::TessellationTolerance::new(max_angle, TOL)
.with_isolines(isolines);
.with_isolines(isolines)
.with_planar_isolines(planar_isolines);
if let Some(deflection) = chordal_deflection {
tolerance = tolerance.with_chordal_deflection(deflection);
}
@@ -267,20 +269,41 @@ pub fn edge_wires(body: &Body) -> Vec<acadrust::entities::Wire> {
.collect()
}
/// White wireframe used while a solid-history grip is hot.
/// White wireframe used while a solid-history grip is hot. Surface previews
/// include the requested construction isolines so their cage follows the edit.
///
/// This deliberately does not touch the resident solid mesh or its entity
/// wires: the selected source stays visible in blue while the candidate body
/// is presented as a separate, non-pickable outline until placement.
pub fn grip_preview_wires(body: &Body, handle: acadrust::Handle) -> Vec<WireModel> {
tessellation(body)
pub fn grip_preview_wires(
body: &Body,
handle: acadrust::Handle,
isolines: usize,
) -> Vec<WireModel> {
let tessellation = brep::mesh::tessellate(
body,
brep::mesh::TessellationTolerance::new(
cadkernel::tessellation::DEFAULT_ANGLE,
TOL,
)
.with_isolines(isolines)
.with_planar_isolines(isolines > 0),
);
tessellation
.edges
.into_iter()
.filter(|edge| edge.positions.len() >= 2)
.map(|edge| {
.map(|edge| edge.positions)
.chain(
tessellation
.isolines
.into_iter()
.map(|isoline| isoline.positions),
)
.filter(|positions| positions.len() >= 2)
.map(|positions| {
WireModel::solid_f64(
format!("{}-GRIP-PREVIEW", handle.value()),
edge.positions,
positions,
WireModel::WHITE,
false,
)
@@ -454,6 +477,7 @@ pub fn display_from_solid(
facet_resolution: f64,
chordal_deflection: Option<f64>,
isolines: usize,
planar_isolines: bool,
) -> Option<(MeshLodSet, Vec<acadrust::entities::Wire>, [f64; 3])> {
use acadrust::types::Vector3;
let tessellation = display_tessellation(
@@ -461,6 +485,7 @@ pub fn display_from_solid(
facet_resolution,
chordal_deflection,
isolines,
planar_isolines,
);
let center = mesh_center(&tessellation.mesh)?;
let wires = tessellation
@@ -541,7 +566,7 @@ mod tests {
use super::*;
fn tri_count(body: &Body) -> usize {
display_from_solid(body, [0.7, 0.7, 0.7, 1.0], 1.0, None, 0)
display_from_solid(body, [0.7, 0.7, 0.7, 1.0], 1.0, None, 0, false)
.map(|(m, _, _)| m.lods[0].indices.len() / 3)
.unwrap_or(0)
}
+9 -1
View File
@@ -1043,7 +1043,15 @@ impl Scene {
}
self.solid_history_preview_wires.insert(
handle,
crate::scene::model::solid_model::grip_preview_wires(&body, handle),
crate::scene::model::solid_model::grip_preview_wires(
&body,
handle,
if matches!(self.document.get_entity(handle), Some(EntityType::Surface(_))) {
self.document.header.isolines.max(0) as usize
} else {
0
},
),
);
true
}