feat(model): edit primitive history properties
This commit is contained in:
parent
4ac10187c2
commit
59ca0f693f
5 changed files with 257 additions and 12 deletions
|
|
@ -358,6 +358,12 @@ impl OpenCADStudio {
|
||||||
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
||||||
let mut sections =
|
let mut sections =
|
||||||
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
||||||
|
sections.extend(
|
||||||
|
crate::scene::model::solid_history::primitive_properties(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Turn the Material row into an editable picker: the source
|
// Turn the Material row into an editable picker: the source
|
||||||
// options (ByLayer / ByBlock) plus every named material the
|
// options (ByLayer / ByBlock) plus every named material the
|
||||||
|
|
@ -1387,7 +1393,11 @@ impl OpenCADStudio {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(handle, entity)| (*handle, entity))
|
.map(|(handle, entity)| (*handle, entity))
|
||||||
.collect();
|
.collect();
|
||||||
let sections = aggregate_sections(&local_refs, &text_style_names);
|
let mut sections = aggregate_sections(&local_refs, &text_style_names);
|
||||||
|
sections.extend(aggregate_solid_history_sections(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
&local_refs.iter().map(|(handle, _)| *handle).collect::<Vec<_>>(),
|
||||||
|
));
|
||||||
ui::PropertiesPanel {
|
ui::PropertiesPanel {
|
||||||
choice_combos: sections
|
choice_combos: sections
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1903,6 +1913,28 @@ pub(super) fn aggregate_sections(
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn aggregate_solid_history_sections(
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
handles: &[Handle],
|
||||||
|
) -> Vec<crate::scene::model::object::PropSection> {
|
||||||
|
let mut sections = handles.iter().map(|handle| {
|
||||||
|
crate::scene::model::solid_history::primitive_properties(document, *handle)
|
||||||
|
});
|
||||||
|
let Some(mut merged) = sections.next() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if merged.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
for next in sections {
|
||||||
|
if next.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
merged = merge_sections(&merged, &next);
|
||||||
|
}
|
||||||
|
merged
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_sections(
|
fn merge_sections(
|
||||||
left: &[crate::scene::model::object::PropSection],
|
left: &[crate::scene::model::object::PropSection],
|
||||||
right: &[crate::scene::model::object::PropSection],
|
right: &[crate::scene::model::object::PropSection],
|
||||||
|
|
|
||||||
|
|
@ -2206,15 +2206,36 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(entity) =
|
if crate::scene::model::solid_history::is_primitive_property(
|
||||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
field,
|
||||||
{
|
) {
|
||||||
crate::scene::view::dispatch::apply_geom_prop_in_working_plane(
|
self.tabs[i].scene.apply_solid_history_property(
|
||||||
entity,
|
handle,
|
||||||
|
field,
|
||||||
|
&val,
|
||||||
|
);
|
||||||
|
} else if self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.apply_solid_position_property(
|
||||||
|
handle,
|
||||||
field,
|
field,
|
||||||
&val,
|
&val,
|
||||||
plane,
|
plane,
|
||||||
);
|
)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
if let Some(entity) = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
crate::scene::view::dispatch::apply_geom_prop_in_working_plane(
|
||||||
|
entity,
|
||||||
|
field,
|
||||||
|
&val,
|
||||||
|
plane,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
// Grippable + PropertyEditable for Solid3D, Region, Body.
|
// Grippable + PropertyEditable for Solid3D, Region, Body.
|
||||||
//
|
//
|
||||||
// Geometry lives in ACIS data — we cannot edit it via the properties panel.
|
// Shared grips and properties for modeler entities.
|
||||||
// We expose the point_of_reference as a translate grip and show ACIS size
|
|
||||||
// as read-only info. Grip translate also updates stored wire points; the
|
|
||||||
// caller translates the mesh vertices to match.
|
|
||||||
|
|
||||||
use acadrust::entities::{Body, Region, Solid3D, Surface};
|
use acadrust::entities::{Body, Region, Solid3D, Surface};
|
||||||
use cadkernel::space::polygon;
|
use cadkernel::space::polygon;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,11 @@ use acadrust::types::{Vector2, Vector3};
|
||||||
use acadrust::EntityType;
|
use acadrust::EntityType;
|
||||||
use cadkernel::brep::Body;
|
use cadkernel::brep::Body;
|
||||||
|
|
||||||
use crate::scene::model::object::{GripApply, GripDef, GripShape};
|
|
||||||
use crate::command::EntityTransform;
|
use crate::command::EntityTransform;
|
||||||
|
use crate::scene::model::object::{
|
||||||
|
GripApply, GripDef, GripShape, PropSection, PropValue, Property,
|
||||||
|
};
|
||||||
|
use crate::t;
|
||||||
|
|
||||||
pub const GRIP_LENGTH: usize = 10_001;
|
pub const GRIP_LENGTH: usize = 10_001;
|
||||||
pub const GRIP_WIDTH: usize = 10_002;
|
pub const GRIP_WIDTH: usize = 10_002;
|
||||||
|
|
@ -19,6 +22,142 @@ pub const GRIP_MAJOR_RADIUS: usize = 10_005;
|
||||||
pub const GRIP_MINOR_RADIUS: usize = 10_006;
|
pub const GRIP_MINOR_RADIUS: usize = 10_006;
|
||||||
pub const GRIP_SIDES: usize = 10_007;
|
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_SIDES: &str = "solid_history_sides";
|
||||||
|
|
||||||
|
fn history_prop(label: &str, field: &'static str, value: impl ToString) -> Property {
|
||||||
|
Property {
|
||||||
|
label: label.to_string(),
|
||||||
|
field,
|
||||||
|
value: PropValue::EditText(value.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn primitive_properties(
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
handle: acadrust::Handle,
|
||||||
|
) -> Vec<PropSection> {
|
||||||
|
let Some(operation) = document.solid_history_operation(handle) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let props = match operation {
|
||||||
|
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => vec![
|
||||||
|
history_prop(t!("Length").as_ref(), PROP_LENGTH, value.length),
|
||||||
|
history_prop(t!("Width").as_ref(), PROP_WIDTH, value.width),
|
||||||
|
history_prop(t!("Height").as_ref(), PROP_HEIGHT, value.height),
|
||||||
|
],
|
||||||
|
SolidHistoryOperation::Cylinder(value) | SolidHistoryOperation::Cone(value) => vec![
|
||||||
|
history_prop(t!("Radius").as_ref(), PROP_RADIUS, value.major_radius),
|
||||||
|
history_prop(t!("Height").as_ref(), PROP_HEIGHT, value.height),
|
||||||
|
],
|
||||||
|
SolidHistoryOperation::Sphere(value) => vec![history_prop(
|
||||||
|
t!("Radius").as_ref(),
|
||||||
|
PROP_RADIUS,
|
||||||
|
value.radius,
|
||||||
|
)],
|
||||||
|
SolidHistoryOperation::Torus(value) => vec![
|
||||||
|
history_prop(
|
||||||
|
t!("Major Radius").as_ref(),
|
||||||
|
PROP_MAJOR_RADIUS,
|
||||||
|
value.major_radius,
|
||||||
|
),
|
||||||
|
history_prop(
|
||||||
|
t!("Minor Radius").as_ref(),
|
||||||
|
PROP_MINOR_RADIUS,
|
||||||
|
value.minor_radius,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
SolidHistoryOperation::Pyramid(value) => vec![
|
||||||
|
history_prop(t!("Radius").as_ref(), PROP_RADIUS, value.radius),
|
||||||
|
history_prop(t!("Height").as_ref(), PROP_HEIGHT, value.height),
|
||||||
|
history_prop(t!("Sides").as_ref(), PROP_SIDES, value.sides),
|
||||||
|
],
|
||||||
|
_ => return Vec::new(),
|
||||||
|
};
|
||||||
|
vec![PropSection {
|
||||||
|
title: t!("Primitive").into_owned(),
|
||||||
|
props,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_primitive_property(field: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
field,
|
||||||
|
PROP_LENGTH
|
||||||
|
| PROP_WIDTH
|
||||||
|
| PROP_HEIGHT
|
||||||
|
| PROP_RADIUS
|
||||||
|
| PROP_MAJOR_RADIUS
|
||||||
|
| PROP_MINOR_RADIUS
|
||||||
|
| PROP_SIDES
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_primitive_property(
|
||||||
|
operation: &mut SolidHistoryOperation,
|
||||||
|
field: &str,
|
||||||
|
value: &str,
|
||||||
|
) -> bool {
|
||||||
|
let Ok(number) = value.trim().parse::<f64>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !number.is_finite() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let positive = || (number > 0.0).then_some(number);
|
||||||
|
match operation {
|
||||||
|
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => match field {
|
||||||
|
PROP_LENGTH => value.length = positive().unwrap_or(value.length),
|
||||||
|
PROP_WIDTH => value.width = positive().unwrap_or(value.width),
|
||||||
|
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
||||||
|
_ => return false,
|
||||||
|
},
|
||||||
|
SolidHistoryOperation::Cylinder(value) | SolidHistoryOperation::Cone(value) => match field {
|
||||||
|
PROP_RADIUS => {
|
||||||
|
let Some(radius) = positive() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
value.major_radius = radius;
|
||||||
|
value.minor_radius = radius;
|
||||||
|
value.x_radius = radius;
|
||||||
|
}
|
||||||
|
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
||||||
|
_ => return false,
|
||||||
|
},
|
||||||
|
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::Pyramid(value) => match field {
|
||||||
|
PROP_RADIUS => value.radius = positive().unwrap_or(value.radius),
|
||||||
|
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
||||||
|
PROP_SIDES => {
|
||||||
|
let rounded = number.round();
|
||||||
|
if (number - rounded).abs() > 1e-9 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let sides = rounded as i32;
|
||||||
|
if !(3..=71).contains(&sides) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value.sides = sides;
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
},
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
positive().is_some() || field == PROP_SIDES
|
||||||
|
}
|
||||||
|
|
||||||
fn matrix(transform: [f64; 16]) -> Option<glam::DMat4> {
|
fn matrix(transform: [f64; 16]) -> Option<glam::DMat4> {
|
||||||
let matrix = glam::DMat4::from_cols_array(&transform);
|
let matrix = glam::DMat4::from_cols_array(&transform);
|
||||||
(matrix.is_finite() && matrix.determinant().abs() > 1e-12).then_some(matrix)
|
(matrix.is_finite() && matrix.determinant().abs() > 1e-12).then_some(matrix)
|
||||||
|
|
|
||||||
|
|
@ -788,6 +788,62 @@ impl Scene {
|
||||||
self.rebuild_solid_history(handle, operation)
|
self.rebuild_solid_history(handle, operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_solid_history_property(
|
||||||
|
&mut self,
|
||||||
|
handle: Handle,
|
||||||
|
field: &str,
|
||||||
|
value: &str,
|
||||||
|
) -> bool {
|
||||||
|
let Some(mut operation) = self.document.solid_history_operation(handle).cloned() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !crate::scene::model::solid_history::apply_primitive_property(
|
||||||
|
&mut operation,
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.rebuild_solid_history(handle, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_solid_position_property(
|
||||||
|
&mut self,
|
||||||
|
handle: Handle,
|
||||||
|
field: &str,
|
||||||
|
value: &str,
|
||||||
|
plane: crate::command::WorkingPlane,
|
||||||
|
) -> Option<bool> {
|
||||||
|
let axis = match field {
|
||||||
|
"s3d_px" | "rgn_px" | "bdy_px" | "srf_px" => 0,
|
||||||
|
"s3d_py" | "rgn_py" | "bdy_py" | "srf_py" => 1,
|
||||||
|
"s3d_pz" | "rgn_pz" | "bdy_pz" | "srf_pz" => 2,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let Ok(target) = value.trim().parse::<f64>() else {
|
||||||
|
return Some(false);
|
||||||
|
};
|
||||||
|
if !target.is_finite() {
|
||||||
|
return Some(false);
|
||||||
|
}
|
||||||
|
let Some(point) = self
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.and_then(crate::entities::solid3d::point_of_reference)
|
||||||
|
else {
|
||||||
|
return Some(false);
|
||||||
|
};
|
||||||
|
let world = glam::DVec3::new(point.x, point.y, point.z);
|
||||||
|
let mut local = plane.to_local(world);
|
||||||
|
local[axis] = target;
|
||||||
|
let delta = plane.to_world(local) - world;
|
||||||
|
if delta.length_squared() <= f64::EPSILON {
|
||||||
|
return Some(true);
|
||||||
|
}
|
||||||
|
self.transform_entities(&[handle], &EntityTransform::Translate(delta));
|
||||||
|
Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn translate_solid_geometry(&mut self, handle: Handle, delta: [f64; 3]) {
|
pub(crate) fn translate_solid_geometry(&mut self, handle: Handle, delta: [f64; 3]) {
|
||||||
if delta.iter().all(|value| value.abs() <= f64::EPSILON) {
|
if delta.iter().all(|value| value.abs() <= f64::EPSILON) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue