forked from 1b-agent/opencadstudio-upstream
fix(model): correct primitive creation and grips
This commit is contained in:
1 parent
59ca0f693f
commit
c4dfa7567d
13 files changed
+353
-84
No files matched your search
+14
-14
@@ -2673,30 +2673,28 @@ impl Scene {
|
||||
})
|
||||
}
|
||||
|
||||
/// Re-evaluate every cached mesh's color through `render_style` so a
|
||||
/// Register a Model-tab solid: cache its B-rep (for boolean ops and the
|
||||
/// exact-geometry save path) and tessellate it into the shaded mesh
|
||||
/// pipeline under `handle`. The body is in the same offset-relative frame
|
||||
/// the mesh pipeline uses, so the mesh is stored as-is (Model-tab geometry
|
||||
/// is authored at world_offset 0).
|
||||
/// Cache and display a Model-tab kernel solid.
|
||||
pub fn register_solid_model(&mut self, handle: Handle, solid: cadkernel::brep::Body) {
|
||||
if let Some(center) = crate::scene::model::solid_model::centre(&solid) {
|
||||
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)
|
||||
{
|
||||
if let Some(EntityType::Solid3D(entity)) = self.document.get_entity_mut(handle) {
|
||||
entity.point_of_reference = acadrust::types::Vector3::new(
|
||||
center[0], center[1], center[2],
|
||||
);
|
||||
entity.wires = wires;
|
||||
entity.silhouettes.clear();
|
||||
}
|
||||
}
|
||||
let entity = self.document.get_entity(handle);
|
||||
let color = entity
|
||||
.map(|e| self.render_style(e).0)
|
||||
.unwrap_or([0.8, 0.8, 0.85, 1.0]);
|
||||
if let Some(mut set) = crate::scene::model::solid_model::mesh_from_solid(&solid, color) {
|
||||
let name = handle.value().to_string();
|
||||
for mesh in &mut set.lods {
|
||||
mesh.name = name.clone();
|
||||
}
|
||||
if let Some(entity) = entity {
|
||||
if let Some(entity) = self.document.get_entity(handle) {
|
||||
crate::scene::model::material_model::resolve_material_with_base(
|
||||
&self.document,
|
||||
entity,
|
||||
@@ -2716,6 +2714,8 @@ impl Scene {
|
||||
);
|
||||
}
|
||||
self.meshes.insert(handle, set);
|
||||
} else {
|
||||
self.meshes.remove(&handle);
|
||||
}
|
||||
self.solid_models.insert(handle, solid);
|
||||
// Only this solid's mesh changed — report just its handle so the mesh /
|
||||
|
||||
@@ -105,6 +105,8 @@ pub struct GripDef {
|
||||
/// along its segment. `None` for shapes that don't need rotation
|
||||
/// (Square, Triangle in non-directional contexts).
|
||||
pub dir: Option<[f32; 2]>,
|
||||
/// World-space axis that constrains this grip's drag.
|
||||
pub axis: Option<glam::DVec3>,
|
||||
}
|
||||
|
||||
/// How to apply a grip drag result.
|
||||
|
||||
@@ -18,16 +18,16 @@ pub const GRIP_LENGTH: usize = 10_001;
|
||||
pub const GRIP_WIDTH: usize = 10_002;
|
||||
pub const GRIP_HEIGHT: usize = 10_003;
|
||||
pub const GRIP_RADIUS: usize = 10_004;
|
||||
pub const GRIP_MAJOR_RADIUS: usize = 10_005;
|
||||
pub const GRIP_MINOR_RADIUS: usize = 10_006;
|
||||
pub const GRIP_OUTER_RADIUS: usize = 10_005;
|
||||
pub const GRIP_INNER_RADIUS: usize = 10_006;
|
||||
pub const GRIP_SIDES: usize = 10_007;
|
||||
|
||||
pub const PROP_LENGTH: &str = "solid_history_length";
|
||||
pub const PROP_WIDTH: &str = "solid_history_width";
|
||||
pub const PROP_HEIGHT: &str = "solid_history_height";
|
||||
pub const PROP_RADIUS: &str = "solid_history_radius";
|
||||
pub const PROP_MAJOR_RADIUS: &str = "solid_history_major_radius";
|
||||
pub const PROP_MINOR_RADIUS: &str = "solid_history_minor_radius";
|
||||
pub const PROP_OUTER_RADIUS: &str = "solid_history_outer_radius";
|
||||
pub const PROP_INNER_RADIUS: &str = "solid_history_inner_radius";
|
||||
pub const PROP_SIDES: &str = "solid_history_sides";
|
||||
|
||||
fn history_prop(label: &str, field: &'static str, value: impl ToString) -> Property {
|
||||
@@ -62,14 +62,14 @@ pub fn primitive_properties(
|
||||
)],
|
||||
SolidHistoryOperation::Torus(value) => vec![
|
||||
history_prop(
|
||||
t!("Major Radius").as_ref(),
|
||||
PROP_MAJOR_RADIUS,
|
||||
value.major_radius,
|
||||
t!("Outer Radius").as_ref(),
|
||||
PROP_OUTER_RADIUS,
|
||||
value.major_radius + value.minor_radius,
|
||||
),
|
||||
history_prop(
|
||||
t!("Minor Radius").as_ref(),
|
||||
PROP_MINOR_RADIUS,
|
||||
value.minor_radius,
|
||||
t!("Inner Radius").as_ref(),
|
||||
PROP_INNER_RADIUS,
|
||||
(value.major_radius - value.minor_radius).max(0.0),
|
||||
),
|
||||
],
|
||||
SolidHistoryOperation::Pyramid(value) => vec![
|
||||
@@ -92,8 +92,8 @@ pub fn is_primitive_property(field: &str) -> bool {
|
||||
| PROP_WIDTH
|
||||
| PROP_HEIGHT
|
||||
| PROP_RADIUS
|
||||
| PROP_MAJOR_RADIUS
|
||||
| PROP_MINOR_RADIUS
|
||||
| PROP_OUTER_RADIUS
|
||||
| PROP_INNER_RADIUS
|
||||
| PROP_SIDES
|
||||
)
|
||||
}
|
||||
@@ -132,11 +132,33 @@ pub fn apply_primitive_property(
|
||||
SolidHistoryOperation::Sphere(value) if field == PROP_RADIUS => {
|
||||
value.radius = positive().unwrap_or(value.radius);
|
||||
}
|
||||
SolidHistoryOperation::Torus(value) => match field {
|
||||
PROP_MAJOR_RADIUS => value.major_radius = positive().unwrap_or(value.major_radius),
|
||||
PROP_MINOR_RADIUS => value.minor_radius = positive().unwrap_or(value.minor_radius),
|
||||
_ => return false,
|
||||
},
|
||||
SolidHistoryOperation::Torus(value) => {
|
||||
let outer = value.major_radius + value.minor_radius;
|
||||
let inner = (value.major_radius - value.minor_radius).max(0.0);
|
||||
match field {
|
||||
PROP_OUTER_RADIUS => {
|
||||
let Some(next_outer) = positive() else {
|
||||
return false;
|
||||
};
|
||||
if next_outer <= inner {
|
||||
return false;
|
||||
}
|
||||
value.major_radius = (next_outer + inner) * 0.5;
|
||||
value.minor_radius = (next_outer - inner) * 0.5;
|
||||
}
|
||||
PROP_INNER_RADIUS => {
|
||||
let Some(next_inner) = positive() else {
|
||||
return false;
|
||||
};
|
||||
if next_inner >= outer {
|
||||
return false;
|
||||
}
|
||||
value.major_radius = (outer + next_inner) * 0.5;
|
||||
value.minor_radius = (outer - next_inner) * 0.5;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
SolidHistoryOperation::Pyramid(value) => match field {
|
||||
PROP_RADIUS => value.radius = positive().unwrap_or(value.radius),
|
||||
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
||||
@@ -232,17 +254,28 @@ fn world_point(transform: [f64; 16], point: [f64; 3]) -> Option<glam::DVec3> {
|
||||
Some(matrix(transform)?.transform_point3(glam::DVec3::from_array(point)))
|
||||
}
|
||||
|
||||
fn world_vector(transform: [f64; 16], vector: [f64; 3]) -> Option<glam::DVec3> {
|
||||
let vector = matrix(transform)?.transform_vector3(glam::DVec3::from_array(vector));
|
||||
(vector.length_squared() > 1e-12).then(|| vector.normalize())
|
||||
}
|
||||
|
||||
fn local_point(transform: [f64; 16], point: glam::DVec3) -> Option<glam::DVec3> {
|
||||
Some(matrix(transform)?.inverse().transform_point3(point))
|
||||
}
|
||||
|
||||
fn grip(id: usize, world: glam::DVec3, shape: GripShape) -> GripDef {
|
||||
fn grip(
|
||||
id: usize,
|
||||
world: glam::DVec3,
|
||||
shape: GripShape,
|
||||
axis: Option<glam::DVec3>,
|
||||
) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
world,
|
||||
is_midpoint: false,
|
||||
shape,
|
||||
dir: None,
|
||||
axis,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,9 +287,14 @@ pub fn primitive_grips(
|
||||
return Vec::new();
|
||||
};
|
||||
let mut grips = Vec::new();
|
||||
let mut add = |id, transform, point, shape| {
|
||||
let mut add = |id, transform, point, shape, axis: Option<[f64; 3]>| {
|
||||
if let Some(world) = world_point(transform, point) {
|
||||
grips.push(grip(id, world, shape));
|
||||
grips.push(grip(
|
||||
id,
|
||||
world,
|
||||
shape,
|
||||
axis.and_then(|vector| world_vector(transform, vector)),
|
||||
));
|
||||
}
|
||||
};
|
||||
match operation {
|
||||
@@ -266,18 +304,21 @@ pub fn primitive_grips(
|
||||
value.base.transform,
|
||||
[value.length, value.width * 0.5, 0.0],
|
||||
GripShape::Square,
|
||||
None,
|
||||
);
|
||||
add(
|
||||
GRIP_WIDTH,
|
||||
value.base.transform,
|
||||
[value.length * 0.5, value.width, 0.0],
|
||||
GripShape::Square,
|
||||
None,
|
||||
);
|
||||
add(
|
||||
GRIP_HEIGHT,
|
||||
value.base.transform,
|
||||
[value.length * 0.5, value.width * 0.5, value.height],
|
||||
GripShape::Square,
|
||||
Some([0.0, 0.0, 1.0]),
|
||||
);
|
||||
}
|
||||
SolidHistoryOperation::Cylinder(value) | SolidHistoryOperation::Cone(value) => {
|
||||
@@ -286,12 +327,14 @@ pub fn primitive_grips(
|
||||
value.base.transform,
|
||||
[value.major_radius, 0.0, value.height * 0.5],
|
||||
GripShape::Square,
|
||||
None,
|
||||
);
|
||||
add(
|
||||
GRIP_HEIGHT,
|
||||
value.base.transform,
|
||||
[0.0, 0.0, value.height],
|
||||
GripShape::Square,
|
||||
Some([0.0, 0.0, 1.0]),
|
||||
);
|
||||
}
|
||||
SolidHistoryOperation::Sphere(value) => add(
|
||||
@@ -299,19 +342,26 @@ pub fn primitive_grips(
|
||||
value.base.transform,
|
||||
[value.radius, 0.0, 0.0],
|
||||
GripShape::Square,
|
||||
None,
|
||||
),
|
||||
SolidHistoryOperation::Torus(value) => {
|
||||
add(
|
||||
GRIP_MAJOR_RADIUS,
|
||||
value.base.transform,
|
||||
[value.major_radius, 0.0, 0.0],
|
||||
GripShape::Square,
|
||||
);
|
||||
add(
|
||||
GRIP_MINOR_RADIUS,
|
||||
GRIP_OUTER_RADIUS,
|
||||
value.base.transform,
|
||||
[value.major_radius + value.minor_radius, 0.0, 0.0],
|
||||
GripShape::Square,
|
||||
None,
|
||||
);
|
||||
add(
|
||||
GRIP_INNER_RADIUS,
|
||||
value.base.transform,
|
||||
[
|
||||
(value.major_radius - value.minor_radius).max(0.0),
|
||||
0.0,
|
||||
0.0,
|
||||
],
|
||||
GripShape::Square,
|
||||
None,
|
||||
);
|
||||
}
|
||||
SolidHistoryOperation::Pyramid(value) => {
|
||||
@@ -320,12 +370,14 @@ pub fn primitive_grips(
|
||||
value.base.transform,
|
||||
[value.radius, 0.0, 0.0],
|
||||
GripShape::Square,
|
||||
None,
|
||||
);
|
||||
add(
|
||||
GRIP_HEIGHT,
|
||||
value.base.transform,
|
||||
[0.0, 0.0, value.height],
|
||||
GripShape::Square,
|
||||
Some([0.0, 0.0, 1.0]),
|
||||
);
|
||||
let angle = (value.sides.clamp(3, 71) as f64 * 5.0).to_radians();
|
||||
add(
|
||||
@@ -333,6 +385,7 @@ pub fn primitive_grips(
|
||||
value.base.transform,
|
||||
[value.radius * angle.cos(), value.radius * angle.sin(), 0.0],
|
||||
GripShape::Triangle,
|
||||
None,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
@@ -360,7 +413,7 @@ pub fn apply_primitive_grip(
|
||||
match grip_id {
|
||||
GRIP_LENGTH => value.length = positive(local.x),
|
||||
GRIP_WIDTH => value.width = positive(local.y),
|
||||
GRIP_HEIGHT => value.height = positive(local.z),
|
||||
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
@@ -372,7 +425,7 @@ pub fn apply_primitive_grip(
|
||||
value.minor_radius = radius;
|
||||
value.x_radius = radius;
|
||||
}
|
||||
GRIP_HEIGHT => value.height = positive(local.z),
|
||||
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
@@ -380,19 +433,29 @@ pub fn apply_primitive_grip(
|
||||
value.radius = local.length().max(1e-6);
|
||||
}
|
||||
SolidHistoryOperation::Torus(value) => match grip_id {
|
||||
GRIP_MAJOR_RADIUS => {
|
||||
value.major_radius = local.x.hypot(local.y).max(1e-6)
|
||||
GRIP_OUTER_RADIUS => {
|
||||
let outer = local.x.hypot(local.y).max(1e-6);
|
||||
let inner = (value.major_radius - value.minor_radius).max(1e-6);
|
||||
if outer <= inner {
|
||||
return false;
|
||||
}
|
||||
value.major_radius = (outer + inner) * 0.5;
|
||||
value.minor_radius = (outer - inner) * 0.5;
|
||||
}
|
||||
GRIP_MINOR_RADIUS => {
|
||||
value.minor_radius = (local.x.hypot(local.y) - value.major_radius)
|
||||
.abs()
|
||||
.max(1e-6)
|
||||
GRIP_INNER_RADIUS => {
|
||||
let inner = local.x.hypot(local.y).max(1e-6);
|
||||
let outer = value.major_radius + value.minor_radius;
|
||||
if inner >= outer {
|
||||
return false;
|
||||
}
|
||||
value.major_radius = (outer + inner) * 0.5;
|
||||
value.minor_radius = (outer - inner) * 0.5;
|
||||
}
|
||||
_ => return false,
|
||||
},
|
||||
SolidHistoryOperation::Pyramid(value) => match grip_id {
|
||||
GRIP_RADIUS => value.radius = local.x.hypot(local.y).max(1e-6),
|
||||
GRIP_HEIGHT => value.height = positive(local.z),
|
||||
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
||||
GRIP_SIDES => {
|
||||
let angle = local.y.atan2(local.x).rem_euclid(std::f64::consts::TAU);
|
||||
value.sides = (angle.to_degrees() / 5.0).round() as i32;
|
||||
|
||||
@@ -211,11 +211,11 @@ pub fn edge_wires(body: &Body) -> Vec<acadrust::entities::Wire> {
|
||||
use acadrust::types::Vector3;
|
||||
tessellation(body)
|
||||
.edges
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|edge| {
|
||||
acadrust::entities::Wire::from_points(
|
||||
edge.positions
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p[0], p[1], p[2]))
|
||||
.collect(),
|
||||
)
|
||||
@@ -254,7 +254,13 @@ pub fn boolean(op: Bool, a: &Body, b: &Body) -> Option<Body> {
|
||||
/// Tessellate a `Body` into a single-LOD `MeshLodSet` (world-space, before
|
||||
/// world_offset is applied by the caller).
|
||||
pub fn mesh_from_solid(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
let tessellation = tessellation(body);
|
||||
mesh_from_tessellation(tessellation(body), color)
|
||||
}
|
||||
|
||||
fn mesh_from_tessellation(
|
||||
tessellation: brep::mesh::BodyMesh,
|
||||
color: [f32; 4],
|
||||
) -> Option<MeshLodSet> {
|
||||
let silhouette = tessellation.silhouette_source();
|
||||
let mesh = tessellation.mesh;
|
||||
if mesh.is_empty() {
|
||||
@@ -313,12 +319,37 @@ pub fn mesh_from_solid(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
Some(set)
|
||||
}
|
||||
|
||||
pub fn display_from_solid(
|
||||
body: &Body,
|
||||
color: [f32; 4],
|
||||
) -> Option<(MeshLodSet, Vec<acadrust::entities::Wire>, [f64; 3])> {
|
||||
use acadrust::types::Vector3;
|
||||
let tessellation = tessellation(body);
|
||||
let center = mesh_center(&tessellation.mesh)?;
|
||||
let wires = tessellation
|
||||
.edges
|
||||
.iter()
|
||||
.map(|edge| {
|
||||
acadrust::entities::Wire::from_points(
|
||||
edge.positions
|
||||
.iter()
|
||||
.map(|point| Vector3::new(point[0], point[1], point[2]))
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Some((mesh_from_tessellation(tessellation, color)?, wires, center))
|
||||
}
|
||||
|
||||
/// The middle of a body, for a caller needing a point to turn or scale about.
|
||||
///
|
||||
/// Read off the mesh rather than `body_bounds`, which refuses a face that
|
||||
/// wraps a closed surface — a sphere is one such face and has no box at all.
|
||||
pub fn centre(body: &Body) -> Option<[f64; 3]> {
|
||||
let mesh = tessellation(body).mesh;
|
||||
mesh_center(&tessellation(body).mesh)
|
||||
}
|
||||
|
||||
fn mesh_center(mesh: &brep::mesh::Mesh) -> Option<[f64; 3]> {
|
||||
if mesh.positions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
+51
-8
@@ -728,7 +728,6 @@ impl Scene {
|
||||
let Some(document) = crate::scene::convert::acis_export::planar_solid_to_sat(&body) else {
|
||||
return false;
|
||||
};
|
||||
let wires = crate::scene::model::solid_model::edge_wires(&body);
|
||||
self.record_solid_history_before(handle);
|
||||
if self
|
||||
.document
|
||||
@@ -741,8 +740,46 @@ impl Scene {
|
||||
return false;
|
||||
};
|
||||
entity.set_sat_document(&document);
|
||||
entity.wires = wires;
|
||||
entity.silhouettes.clear();
|
||||
self.register_solid_model(handle, body);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn finalize_solid_history(&mut self, handle: Handle) -> bool {
|
||||
let Some(operation) = self.document.solid_history_operation(handle).cloned() else {
|
||||
return false;
|
||||
};
|
||||
let Ok(body) = cadkernel::acis::rebuild_body(&operation) else {
|
||||
return false;
|
||||
};
|
||||
let Some(document) = crate::scene::convert::acis_export::planar_solid_to_sat(&body) else {
|
||||
return false;
|
||||
};
|
||||
let Some(EntityType::Solid3D(entity)) = self.document.get_entity_mut(handle) else {
|
||||
return false;
|
||||
};
|
||||
entity.set_sat_document(&document);
|
||||
self.register_solid_model(handle, body);
|
||||
true
|
||||
}
|
||||
|
||||
fn preview_solid_history(
|
||||
&mut self,
|
||||
handle: Handle,
|
||||
operation: acadrust::objects::SolidHistoryOperation,
|
||||
) -> bool {
|
||||
let Ok(body) = cadkernel::acis::rebuild_body(&operation) else {
|
||||
return false;
|
||||
};
|
||||
if self
|
||||
.document
|
||||
.update_solid_history(handle, operation)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let Some(EntityType::Solid3D(_)) = self.document.get_entity(handle) else {
|
||||
return false;
|
||||
};
|
||||
self.register_solid_model(handle, body);
|
||||
true
|
||||
}
|
||||
@@ -761,12 +798,18 @@ impl Scene {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if self.rebuild_solid_history(handle, operation.clone()) {
|
||||
if let EntityTransform::Translate(delta) = transform {
|
||||
if self
|
||||
.document
|
||||
.update_solid_history(handle, operation)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.translate_solid_geometry(handle, delta.to_array());
|
||||
return true;
|
||||
}
|
||||
self.record_solid_history_before(handle);
|
||||
let _ = self.document.update_solid_history(handle, operation);
|
||||
false
|
||||
self.rebuild_solid_history(handle, operation)
|
||||
}
|
||||
|
||||
fn apply_solid_history_grip(
|
||||
@@ -785,7 +828,7 @@ impl Scene {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
self.rebuild_solid_history(handle, operation)
|
||||
self.preview_solid_history(handle, operation)
|
||||
}
|
||||
|
||||
pub fn apply_solid_history_property(
|
||||
|
||||
@@ -26,6 +26,8 @@ pub struct GripEdit {
|
||||
pub last_world: DVec3,
|
||||
/// How cursor movement modifies the selected grip.
|
||||
pub mode: GripEditMode,
|
||||
/// Optional world-space drag axis.
|
||||
pub axis: Option<DVec3>,
|
||||
/// Every hot grip moved by this edit. A normal grip edit contains one target.
|
||||
pub targets: Vec<GripTarget>,
|
||||
}
|
||||
@@ -57,6 +59,7 @@ impl GripEdit {
|
||||
origin_world: world,
|
||||
last_world: world,
|
||||
mode: GripEditMode::Stretch,
|
||||
axis: None,
|
||||
targets: vec![GripTarget {
|
||||
handle,
|
||||
grip_id,
|
||||
|
||||
Reference in new issue
Block a user