forked from 1b-agent/opencadstudio-upstream
Merge pull request #980 from ramox81/codex/extrude-grip-preview
Fix extruded solid grip preview and taper range
This commit is contained in:
8 files changed
+94
-16
No files matched your search
Generated
+1
-1
@@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=fb5a5c18f794df30e97c712fb5d4fe7c6629d1d5#fb5a5c18f794df30e97c712fb5d4fe7c6629d1d5"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=ac2e69070278bf56ae44829f31a69f6a1006ac41#ac2e69070278bf56ae44829f31a69f6a1006ac41"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
|
||||
+1
-1
@@ -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 = "393d6c0857447f9ca678bed97a612707f0a842c6", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "fb5a5c18f794df30e97c712fb5d4fe7c6629d1d5", features = ["acis", "offset"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "ac2e69070278bf56ae44829f31a69f6a1006ac41", features = ["acis", "offset"] }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -1685,14 +1685,21 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
// Current deformed geometry.
|
||||
let mut preview =
|
||||
self.tabs[i].scene.wire_models_for(&edited_handles);
|
||||
self.tabs[i].scene.grip_wire_models_for(&edited_handles);
|
||||
|
||||
// Also show the drag-start geometry as a faint ghost.
|
||||
//
|
||||
// This is display-only. Preview wires never participate in the resident
|
||||
// hit-test/snap set, so keeping the original shape visible does not
|
||||
// reintroduce self-snapping.
|
||||
let mut reference = self.grip_reference_wires.clone();
|
||||
let keep_reference_overlay = edited_handles
|
||||
.iter()
|
||||
.any(|handle| self.tabs[i].scene.preview_hidden.contains(handle));
|
||||
let mut reference = if keep_reference_overlay {
|
||||
self.grip_reference_wires.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
for wire in &mut reference {
|
||||
wire.selected = false;
|
||||
@@ -2776,13 +2783,14 @@ impl OpenCADStudio {
|
||||
// press starts a box selection, so without this they'd only
|
||||
// close on the second click (issue #104).
|
||||
self.tabs[i].properties.color_picker_open = false;
|
||||
// Click anywhere outside the popup dismisses it. The
|
||||
// menu's own buttons live above this mouse_area, so a
|
||||
// press that reaches here means the cursor is not on
|
||||
// any menu item.
|
||||
if self.grip_popup.take().is_some() {
|
||||
// Click anywhere outside the popup dismisses it. A hover popup must
|
||||
// not swallow a grip click beneath it; a pinned popup was opened by an
|
||||
// explicit click, so its outside click only dismisses the menu.
|
||||
if let Some(popup) = self.grip_popup.take() {
|
||||
self.grip_hover = None;
|
||||
return Task::none();
|
||||
if popup.pinned {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
// Outside-click dismiss for the visibility-state dropdown
|
||||
// (its buttons sit above this mouse_area).
|
||||
|
||||
@@ -1476,6 +1476,9 @@ pub struct Scene {
|
||||
pub selection_filter: HashSet<String>,
|
||||
/// In-progress preview wires while a command is active (rubber-band + object ghosts).
|
||||
pub preview_wires: Vec<WireModel>,
|
||||
/// Candidate wireframes produced by editable solid-history grips. The
|
||||
/// resident body remains untouched until the grip is committed.
|
||||
solid_history_preview_wires: HashMap<Handle, Vec<WireModel>>,
|
||||
/// Live hatch fill used by interactive edits such as dragging the pattern
|
||||
/// origin grip. Kept separate from the resident hatch set so moving one
|
||||
/// pattern never re-uploads every hatch in a large drawing.
|
||||
@@ -1883,6 +1886,7 @@ impl Scene {
|
||||
model_lineweight_scale: 1.0,
|
||||
selection_filter: HashSet::default(),
|
||||
preview_wires: vec![],
|
||||
solid_history_preview_wires: HashMap::default(),
|
||||
preview_hatches: Arc::new(Vec::new()),
|
||||
preview_text: vec![],
|
||||
interim_wire: None,
|
||||
|
||||
@@ -2314,7 +2314,7 @@ fn apply_extrusion_draft_grip(value: &mut SolidHistorySweep, apply: GripApply) -
|
||||
let GripApply::Absolute(world) = apply else {
|
||||
return false;
|
||||
};
|
||||
let Some((_, base_top, radial, profile_radius, height)) = extrusion_draft_grip(value) else {
|
||||
let Some((_, base_top, radial, _, height)) = extrusion_draft_grip(value) else {
|
||||
return false;
|
||||
};
|
||||
let top_offset = (world - base_top).dot(radial);
|
||||
@@ -2322,10 +2322,27 @@ fn apply_extrusion_draft_grip(value: &mut SolidHistorySweep, apply: GripApply) -
|
||||
return false;
|
||||
}
|
||||
let limit = 89.0_f64.to_radians();
|
||||
let inward_limit = ((profile_radius - 1e-6).max(1e-6) / height)
|
||||
.atan()
|
||||
.min(limit);
|
||||
let angle = (-top_offset).atan2(height).clamp(-limit, inward_limit);
|
||||
let desired = (-top_offset).atan2(height).clamp(-limit, limit);
|
||||
let angle = if desired > 0.0 && !extrusion_draft_rebuilds(value, desired) {
|
||||
// The maximum inset is profile-dependent. A corner radius is too
|
||||
// restrictive for many outlines, while allowing the pointer through
|
||||
// a collapsed outline makes the top grow again. Search the actual
|
||||
// rebuild boundary so every supported profile reaches its smallest
|
||||
// valid top without crossing into an inverted result.
|
||||
let mut valid = 0.0;
|
||||
let mut invalid = limit;
|
||||
for _ in 0..16 {
|
||||
let candidate = (valid + invalid) * 0.5;
|
||||
if extrusion_draft_rebuilds(value, candidate) {
|
||||
valid = candidate;
|
||||
} else {
|
||||
invalid = candidate;
|
||||
}
|
||||
}
|
||||
valid
|
||||
} else {
|
||||
desired
|
||||
};
|
||||
if !angle.is_finite() || (angle - value.draft_angle).abs() <= 1e-12 {
|
||||
return false;
|
||||
}
|
||||
@@ -2333,6 +2350,12 @@ fn apply_extrusion_draft_grip(value: &mut SolidHistorySweep, apply: GripApply) -
|
||||
true
|
||||
}
|
||||
|
||||
fn extrusion_draft_rebuilds(value: &SolidHistorySweep, angle: f64) -> bool {
|
||||
let mut candidate = value.clone();
|
||||
candidate.draft_angle = angle;
|
||||
cadkernel::acis::rebuild_body(&SolidHistoryOperation::Extrusion(candidate)).is_ok()
|
||||
}
|
||||
|
||||
fn grip(
|
||||
id: usize,
|
||||
world: glam::DVec3,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use cadkernel::brep::{self, Body, Curve3, EdgeKey, FaceKey, Surface};
|
||||
|
||||
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
/// What counts as the same point when the kernel checks a body over.
|
||||
const TOL: f64 = 1e-9;
|
||||
@@ -243,6 +244,27 @@ pub fn edge_wires(body: &Body) -> Vec<acadrust::entities::Wire> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// White wireframe used while a solid-history grip is hot.
|
||||
///
|
||||
/// 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)
|
||||
.edges
|
||||
.into_iter()
|
||||
.filter(|edge| edge.positions.len() >= 2)
|
||||
.map(|edge| {
|
||||
WireModel::solid_f64(
|
||||
format!("{}-GRIP-PREVIEW", handle.value()),
|
||||
edge.positions,
|
||||
WireModel::WHITE,
|
||||
false,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// B-rep edge nearest a world-space surface pick.
|
||||
pub fn nearest_edge(body: &Body, pick: [f64; 3]) -> Option<EdgeKey> {
|
||||
let pick = cadkernel::space::Vec3::from(pick);
|
||||
|
||||
+6
-1
@@ -841,6 +841,7 @@ impl Scene {
|
||||
handle: Handle,
|
||||
operation: acadrust::objects::SolidHistoryOperation,
|
||||
) -> bool {
|
||||
self.solid_history_preview_wires.remove(&handle);
|
||||
let Ok(body) = cadkernel::acis::rebuild_body(&operation) else {
|
||||
return false;
|
||||
};
|
||||
@@ -879,6 +880,7 @@ impl Scene {
|
||||
};
|
||||
entity.set_sat_document(&document);
|
||||
self.sync_solid_reference_point(handle);
|
||||
self.solid_history_preview_wires.remove(&handle);
|
||||
self.register_solid_model(handle, body);
|
||||
true
|
||||
}
|
||||
@@ -901,7 +903,10 @@ impl Scene {
|
||||
let Some(EntityType::Solid3D(_)) = self.document.get_entity(handle) else {
|
||||
return false;
|
||||
};
|
||||
self.register_solid_model(handle, body);
|
||||
self.solid_history_preview_wires.insert(
|
||||
handle,
|
||||
crate::scene::model::solid_model::grip_preview_wires(&body, handle),
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ impl Scene {
|
||||
// flips the wire content id back to the base tessellation id, which
|
||||
// re-uploads the base wires (without the preview) on the next frame.
|
||||
self.preview_wires = vec![];
|
||||
self.solid_history_preview_wires.clear();
|
||||
if !self.preview_hatches.is_empty() {
|
||||
self.preview_hatches = std::sync::Arc::new(Vec::new());
|
||||
}
|
||||
@@ -302,6 +303,21 @@ impl Scene {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Current candidate geometry for a hot grip. Solid-history candidates
|
||||
/// live outside the document until placement; ordinary entities continue
|
||||
/// to use their current tessellation.
|
||||
pub fn grip_wire_models_for(&self, handles: &[acadrust::Handle]) -> Vec<WireModel> {
|
||||
handles
|
||||
.iter()
|
||||
.flat_map(|handle| {
|
||||
self.solid_history_preview_wires
|
||||
.get(handle)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.wire_models_for(std::slice::from_ref(handle)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Split a MIRROR selection into plain ghost wires (reflected wholesale) and
|
||||
/// text ghosts paired with their bounding-box centre. Lets the preview match
|
||||
/// the commit for TEXT: MIRRTEXT on → true glyph mirror (full reflection, same
|
||||
|
||||
Reference in new issue
Block a user