forked from 1b-agent/opencadstudio-upstream
Fix revolve angle and wireframe display
This commit is contained in:
1 parent
5bbb0bbe1f
commit
1a913ee640
8 files changed
+138
-26
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=7e6a651c9493bd52f6abcdaccf508d346766cda5#7e6a651c9493bd52f6abcdaccf508d346766cda5"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=65aa99ef1ab2c5632d61e61baef7c56a27027d4b#65aa99ef1ab2c5632d61e61baef7c56a27027d4b"
|
||||
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 = "7e6a651c9493bd52f6abcdaccf508d346766cda5", features = ["acis", "offset"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "65aa99ef1ab2c5632d61e61baef7c56a27027d4b", features = ["acis", "offset"] }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -796,7 +796,8 @@ impl OpenCADStudio {
|
||||
use crate::modules::insert::solid3d_cmds::RevolveCommand;
|
||||
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
|
||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||
let mut cmd = RevolveCommand::new(color);
|
||||
let isolines = self.tabs[i].scene.document.header.isolines.max(0) as usize;
|
||||
let mut cmd = RevolveCommand::new(color, isolines);
|
||||
if !selected.is_empty() {
|
||||
cmd.set_preselection(
|
||||
selected
|
||||
|
||||
+11
-2
@@ -36,7 +36,13 @@ impl super::OpenCADStudio {
|
||||
return Handle::NULL;
|
||||
};
|
||||
self.tabs[i].scene.create_solid_history(handle, history);
|
||||
self.tabs[i].scene.register_solid_model(handle, solid);
|
||||
if !self.tabs[i].scene.register_solid_model(handle, solid) {
|
||||
// A command result is not successful until it has renderable
|
||||
// geometry. Roll the new entity back so DELOBJ cannot consume a
|
||||
// visible source in exchange for an invisible result.
|
||||
self.tabs[i].scene.rollback_new_entities(&[handle]);
|
||||
return Handle::NULL;
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
@@ -63,7 +69,10 @@ impl super::OpenCADStudio {
|
||||
let Some(handle) = self.commit_entity_handle(entity) else {
|
||||
return Handle::NULL;
|
||||
};
|
||||
self.tabs[i].scene.register_solid_model(handle, surface);
|
||||
if !self.tabs[i].scene.register_solid_model(handle, surface) {
|
||||
self.tabs[i].scene.rollback_new_entities(&[handle]);
|
||||
return Handle::NULL;
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
|
||||
@@ -156,22 +156,28 @@ impl ExtrudeCommand {
|
||||
self.taper_angle,
|
||||
)
|
||||
};
|
||||
body.map(|body| preview_body_wires(&body, self.color))
|
||||
body.map(|body| preview_body_wires(&body, self.color, 0))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn preview_body_wires(body: &cadkernel::brep::Body, color: [f32; 4]) -> Vec<WireModel> {
|
||||
fn preview_body_wires(
|
||||
body: &cadkernel::brep::Body,
|
||||
color: [f32; 4],
|
||||
isolines: usize,
|
||||
) -> Vec<WireModel> {
|
||||
let mesh = cadkernel::brep::mesh::tessellate(
|
||||
body,
|
||||
cadkernel::brep::mesh::TessellationTolerance::new(
|
||||
cadkernel::tessellation::DEFAULT_ANGLE,
|
||||
1e-9,
|
||||
),
|
||||
)
|
||||
.with_isolines(isolines),
|
||||
);
|
||||
mesh.edges
|
||||
let mut wires = mesh
|
||||
.edges
|
||||
.into_iter()
|
||||
.filter(|edge| edge.positions.len() >= 2)
|
||||
.map(|edge| {
|
||||
@@ -182,7 +188,21 @@ fn preview_body_wires(body: &cadkernel::brep::Body, color: [f32; 4]) -> Vec<Wire
|
||||
false,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
.collect::<Vec<_>>();
|
||||
wires.extend(
|
||||
mesh.isolines
|
||||
.into_iter()
|
||||
.filter(|line| line.positions.len() >= 2)
|
||||
.map(|line| {
|
||||
WireModel::solid_f64(
|
||||
"REVOLVE-PREVIEW-ISOLINE".to_owned(),
|
||||
line.positions,
|
||||
color,
|
||||
false,
|
||||
)
|
||||
}),
|
||||
);
|
||||
wires
|
||||
}
|
||||
|
||||
impl CadCommand for ExtrudeCommand {
|
||||
@@ -626,6 +646,7 @@ pub struct RevolveCommand {
|
||||
angle: f64,
|
||||
start_angle: f64,
|
||||
reverse: bool,
|
||||
isolines: usize,
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
@@ -660,7 +681,7 @@ enum RevolveStep {
|
||||
}
|
||||
|
||||
impl RevolveCommand {
|
||||
pub fn new(color: [f32; 4]) -> Self {
|
||||
pub fn new(color: [f32; 4], isolines: usize) -> Self {
|
||||
let defaults = *revolve_defaults().lock().unwrap_or_else(|error| error.into_inner());
|
||||
Self {
|
||||
step: RevolveStep::Pick,
|
||||
@@ -675,6 +696,7 @@ impl RevolveCommand {
|
||||
angle: defaults.angle,
|
||||
start_angle: defaults.start_angle,
|
||||
reverse: false,
|
||||
isolines,
|
||||
color,
|
||||
}
|
||||
}
|
||||
@@ -727,14 +749,29 @@ impl RevolveCommand {
|
||||
Some(self.axis_start + axis * (reference - self.axis_start).dot(axis))
|
||||
}
|
||||
|
||||
fn profile_normal(&self) -> Option<DVec3> {
|
||||
self.preview_profiles.iter().find_map(|(_, entity)| {
|
||||
crate::scene::model::sweep_model::extrusion_profile_of(entity)
|
||||
.and_then(|(profile, _)| profile.plane.normal())
|
||||
.map(DVec3::from_array)
|
||||
.and_then(DVec3::try_normalize)
|
||||
})
|
||||
}
|
||||
|
||||
fn cursor_angle(&self, cursor: DVec3, full_at_zero: bool) -> Option<f64> {
|
||||
let axis = (self.axis_end - self.axis_start).try_normalize()?;
|
||||
let anchor = self.angle_anchor()?;
|
||||
let reference = (self.profile_reference()? - anchor).try_normalize()?;
|
||||
let radial = (cursor - (self.axis_start
|
||||
+ axis * (cursor - self.axis_start).dot(axis)))
|
||||
.try_normalize()?;
|
||||
let mut angle = axis.dot(reference.cross(radial)).atan2(reference.dot(radial));
|
||||
// The picked cursor is projected onto the active/profile construction
|
||||
// plane. Measuring it about the 3-D revolution axis collapses almost
|
||||
// every ordinary mouse pick to 0 or 180 degrees, and 0 is the full-turn
|
||||
// shortcut. Measure the scalar input in the profile plane instead;
|
||||
// the resulting angle is still applied about the requested 3-D axis.
|
||||
let normal = self.profile_normal().unwrap_or(self.working_plane.z);
|
||||
let cursor_direction = cursor - anchor;
|
||||
let radial = (cursor_direction - normal * cursor_direction.dot(normal)).try_normalize()?;
|
||||
let mut angle = normal
|
||||
.dot(reference.cross(radial))
|
||||
.atan2(reference.dot(radial));
|
||||
if angle < 0.0 {
|
||||
angle += std::f64::consts::TAU;
|
||||
}
|
||||
@@ -792,7 +829,7 @@ impl RevolveCommand {
|
||||
start_angle,
|
||||
)
|
||||
};
|
||||
body.map(|body| preview_body_wires(&body, self.color))
|
||||
body.map(|body| preview_body_wires(&body, self.color, self.isolines))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
|
||||
+21
-4
@@ -2723,14 +2723,28 @@ impl Scene {
|
||||
}
|
||||
|
||||
/// Cache and display a Model-tab kernel solid.
|
||||
pub fn register_solid_model(&mut self, handle: Handle, solid: cadkernel::brep::Body) {
|
||||
pub fn register_solid_model(
|
||||
&mut self,
|
||||
handle: Handle,
|
||||
solid: cadkernel::brep::Body,
|
||||
) -> bool {
|
||||
let color = self
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.map(|e| self.render_style(e).0)
|
||||
.unwrap_or([0.8, 0.8, 0.85, 1.0]);
|
||||
if let Some((mut set, wires, center)) =
|
||||
crate::scene::model::solid_model::display_from_solid(&solid, color)
|
||||
let facet_resolution = self.document.header.facet_resolution;
|
||||
let chordal_deflection =
|
||||
crate::entities::solid3d::display_deflection(&self.document.header, facet_resolution);
|
||||
let isolines = self.document.header.isolines.max(0) as usize;
|
||||
let displayed = if let Some((mut set, wires, center)) =
|
||||
crate::scene::model::solid_model::display_from_solid(
|
||||
&solid,
|
||||
color,
|
||||
facet_resolution,
|
||||
chordal_deflection,
|
||||
isolines,
|
||||
)
|
||||
{
|
||||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||
let reference = acadrust::types::Vector3::new(center[0], center[1], center[2]);
|
||||
@@ -2772,15 +2786,18 @@ impl Scene {
|
||||
);
|
||||
}
|
||||
self.meshes.insert(handle, set);
|
||||
true
|
||||
} else {
|
||||
self.meshes.remove(&handle);
|
||||
}
|
||||
false
|
||||
};
|
||||
self.solid_models.insert(handle, solid);
|
||||
self.sync_solid_reference_point(handle);
|
||||
// Only this solid's mesh changed — report just its handle so the mesh /
|
||||
// wire caches patch it in rather than rebuilding the whole drawing (and
|
||||
// so a bulk register loop stays O(n), not O(n²)).
|
||||
self.bump_entities(&[(handle, ChangeKind::Modified)]);
|
||||
displayed
|
||||
}
|
||||
|
||||
/// `BACKGROUND` change picks up the new `adapt_to_bg` result without
|
||||
|
||||
@@ -18,6 +18,29 @@ fn tessellation(body: &Body) -> brep::mesh::BodyMesh {
|
||||
)
|
||||
}
|
||||
|
||||
fn display_tessellation(
|
||||
body: &Body,
|
||||
facet_resolution: f64,
|
||||
chordal_deflection: Option<f64>,
|
||||
isolines: usize,
|
||||
) -> brep::mesh::BodyMesh {
|
||||
let resolution = if facet_resolution.is_finite() && facet_resolution > 0.0 {
|
||||
facet_resolution.clamp(0.01, 10.0)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let max_angle = chordal_deflection.map_or_else(
|
||||
|| cadkernel::tessellation::angle_for_resolution(resolution),
|
||||
|_| cadkernel::tessellation::display_angle_for_resolution(resolution),
|
||||
);
|
||||
let mut tolerance = brep::mesh::TessellationTolerance::new(max_angle, TOL)
|
||||
.with_isolines(isolines);
|
||||
if let Some(deflection) = chordal_deflection {
|
||||
tolerance = tolerance.with_chordal_deflection(deflection);
|
||||
}
|
||||
brep::mesh::tessellate(body, tolerance)
|
||||
}
|
||||
|
||||
/// Axis-aligned box from its center and full extents.
|
||||
pub fn box_solid(center: [f64; 3], length: f64, width: f64, height: f64) -> Option<Body> {
|
||||
brep::make::cuboid(
|
||||
@@ -397,8 +420,13 @@ fn mesh_from_tessellation(
|
||||
color,
|
||||
selected: false,
|
||||
});
|
||||
for edge in tessellation.edges {
|
||||
for segment in edge.positions.windows(2) {
|
||||
for positions in tessellation
|
||||
.edges
|
||||
.into_iter()
|
||||
.map(|edge| edge.positions)
|
||||
.chain(tessellation.isolines.into_iter().map(|line| line.positions))
|
||||
{
|
||||
for segment in positions.windows(2) {
|
||||
for point in segment {
|
||||
let high = [point[0] as f32, point[1] as f32, point[2] as f32];
|
||||
set.edge_verts.push(high);
|
||||
@@ -418,9 +446,17 @@ fn mesh_from_tessellation(
|
||||
pub fn display_from_solid(
|
||||
body: &Body,
|
||||
color: [f32; 4],
|
||||
facet_resolution: f64,
|
||||
chordal_deflection: Option<f64>,
|
||||
isolines: usize,
|
||||
) -> Option<(MeshLodSet, Vec<acadrust::entities::Wire>, [f64; 3])> {
|
||||
use acadrust::types::Vector3;
|
||||
let tessellation = tessellation(body);
|
||||
let tessellation = display_tessellation(
|
||||
body,
|
||||
facet_resolution,
|
||||
chordal_deflection,
|
||||
isolines,
|
||||
);
|
||||
let center = mesh_center(&tessellation.mesh)?;
|
||||
let wires = tessellation
|
||||
.edges
|
||||
@@ -496,7 +532,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn tri_count(body: &Body) -> usize {
|
||||
display_from_solid(body, [0.7, 0.7, 0.7, 1.0])
|
||||
display_from_solid(body, [0.7, 0.7, 0.7, 1.0], 1.0, None, 0)
|
||||
.map(|(m, _, _)| m.lods[0].indices.len() / 3)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
+13
-1
@@ -781,7 +781,19 @@ impl Scene {
|
||||
// ── Erase ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub fn erase_entities(&mut self, handles: &[Handle]) {
|
||||
self.remove_entities(handles, true);
|
||||
}
|
||||
|
||||
/// Roll back entities created by the current command after a downstream
|
||||
/// registration failure. These entities were never successful command
|
||||
/// results, so locked-layer edit policy must not prevent their removal.
|
||||
/// The shared removal path keeps first-touch undo state, history objects,
|
||||
/// groups, selection, and derived caches coherent.
|
||||
pub(crate) fn rollback_new_entities(&mut self, handles: &[Handle]) {
|
||||
self.remove_entities(handles, false);
|
||||
}
|
||||
|
||||
fn remove_entities(&mut self, handles: &[Handle], respect_layer_locks: bool) {
|
||||
let erase_handles = self.handles_expanded_for_leader_annotations(handles);
|
||||
|
||||
let mut handle_set: HashSet<Handle> = HashSet::default();
|
||||
@@ -791,7 +803,7 @@ impl Scene {
|
||||
|
||||
for &h in &erase_handles {
|
||||
// Objects on a locked layer can't be erased.
|
||||
if self.is_layer_locked(h) {
|
||||
if respect_layer_locks && self.is_layer_locked(h) {
|
||||
continue;
|
||||
}
|
||||
// Delta-undo: capture the removed entity so an undo can re-insert it.
|
||||
|
||||
Reference in new issue
Block a user