forked from 1b-agent/opencadstudio-upstream
Fix live extrusion surface grip editing
This commit is contained in:
1 parent
c0b254950f
commit
a24d1e6108
11 files changed
+121
-27
No files matched your search
Generated
+1
-1
@@ -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
@@ -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"] }
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
®ion.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,
|
||||
)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user