forked from 1b-agent/opencadstudio-upstream
922 lines
31 KiB
Rust
922 lines
31 KiB
Rust
use acadrust::entities::Solid3D;
|
|
use acadrust::objects::{
|
|
DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep,
|
|
SolidHistoryCylinder, SolidHistoryNodeBase, SolidHistoryOperation,
|
|
SolidHistoryPyramid, SolidHistorySphere, SolidHistoryTorus,
|
|
};
|
|
use cadkernel::brep::Body;
|
|
|
|
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_WIDTH: usize = 10_002;
|
|
pub const GRIP_HEIGHT: usize = 10_003;
|
|
pub const GRIP_RADIUS: usize = 10_004;
|
|
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 GRIP_BOX_CORNER_FIRST: usize = 10_100;
|
|
pub const GRIP_BOX_FACE_X_MIN: usize = 10_110;
|
|
pub const GRIP_BOX_FACE_X_MAX: usize = 10_111;
|
|
pub const GRIP_BOX_FACE_Y_MIN: usize = 10_112;
|
|
pub const GRIP_BOX_FACE_Y_MAX: usize = 10_113;
|
|
pub const GRIP_BOX_FACE_Z_MIN: usize = 10_114;
|
|
pub const GRIP_BOX_FACE_Z_MAX: usize = 10_115;
|
|
|
|
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_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";
|
|
pub const PROP_POSITION_X: &str = "solid_history_position_x";
|
|
pub const PROP_POSITION_Y: &str = "solid_history_position_y";
|
|
pub const PROP_POSITION_Z: &str = "solid_history_position_z";
|
|
pub const PROP_ROTATION: &str = "solid_history_rotation";
|
|
pub const PROP_HISTORY: &str = "solid_history_record";
|
|
pub const PROP_SHOW_HISTORY: &str = "solid_history_show";
|
|
|
|
fn history_prop(label: &str, field: &'static str, value: impl ToString) -> Property {
|
|
Property {
|
|
label: label.to_string(),
|
|
field,
|
|
value: PropValue::EditText(value.to_string()),
|
|
}
|
|
}
|
|
|
|
fn history_flags(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
) -> Option<(bool, bool)> {
|
|
let graph = document.solid_history_graph(handle)?;
|
|
let ObjectType::DynamicBlock(object) = document.objects.get(&graph.root)? else {
|
|
return None;
|
|
};
|
|
let DynamicBlockData::SolidHistory(history) = &object.data else {
|
|
return None;
|
|
};
|
|
Some((history.record_history, history.show_history))
|
|
}
|
|
|
|
pub fn is_box_primitive(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
) -> bool {
|
|
matches!(
|
|
document.solid_history_operation(handle),
|
|
Some(SolidHistoryOperation::Box(_))
|
|
)
|
|
}
|
|
|
|
pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3> {
|
|
match operation {
|
|
SolidHistoryOperation::Box(value) => world_point(
|
|
value.base.transform,
|
|
[value.length * 0.5, value.width * 0.5, 0.0],
|
|
),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn box_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryBox,
|
|
) -> Vec<PropSection> {
|
|
let Some(position) = world_point(
|
|
value.base.transform,
|
|
[value.length * 0.5, value.width * 0.5, 0.0],
|
|
) else {
|
|
return Vec::new();
|
|
};
|
|
let rotation = matrix(value.base.transform)
|
|
.map(|matrix| matrix.x_axis.y.atan2(matrix.x_axis.x))
|
|
.unwrap_or(0.0);
|
|
let (record_history, show_history) = history_flags(document, handle).unwrap_or((false, false));
|
|
let show_value = if show_history { "Yes" } else { "No" };
|
|
vec![
|
|
PropSection {
|
|
title: t!("Geometry").into_owned(),
|
|
props: vec![
|
|
Property {
|
|
label: t!("Solid type").into_owned(),
|
|
field: "solid_history_type",
|
|
value: PropValue::ReadOnly(t!("Box").into_owned()),
|
|
},
|
|
crate::entities::common::edit_prop(
|
|
t!("Position X").as_ref(),
|
|
PROP_POSITION_X,
|
|
position.x,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Position Y").as_ref(),
|
|
PROP_POSITION_Y,
|
|
position.y,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Position Z").as_ref(),
|
|
PROP_POSITION_Z,
|
|
position.z,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Length").as_ref(),
|
|
PROP_LENGTH,
|
|
value.length,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Width").as_ref(),
|
|
PROP_WIDTH,
|
|
value.width,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Height").as_ref(),
|
|
PROP_HEIGHT,
|
|
value.height,
|
|
),
|
|
Property {
|
|
label: t!("Rotation").into_owned(),
|
|
field: PROP_ROTATION,
|
|
value: PropValue::EditText(
|
|
crate::entities::common::format_direction(rotation),
|
|
),
|
|
},
|
|
],
|
|
},
|
|
PropSection {
|
|
title: t!("Solid History").into_owned(),
|
|
props: vec![
|
|
Property {
|
|
label: t!("History").into_owned(),
|
|
field: PROP_HISTORY,
|
|
value: PropValue::Choice {
|
|
selected: if record_history { "Record" } else { "None" }.to_string(),
|
|
options: vec!["None".to_string(), "Record".to_string()],
|
|
},
|
|
},
|
|
Property {
|
|
label: t!("Show History").into_owned(),
|
|
field: PROP_SHOW_HISTORY,
|
|
value: if record_history {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_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) => return box_properties(document, handle, 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!("Outer Radius").as_ref(),
|
|
PROP_OUTER_RADIUS,
|
|
value.major_radius + value.minor_radius,
|
|
),
|
|
history_prop(
|
|
t!("Inner Radius").as_ref(),
|
|
PROP_INNER_RADIUS,
|
|
(value.major_radius - value.minor_radius).max(0.0),
|
|
),
|
|
],
|
|
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_OUTER_RADIUS
|
|
| PROP_INNER_RADIUS
|
|
| PROP_SIDES
|
|
| PROP_POSITION_X
|
|
| PROP_POSITION_Y
|
|
| PROP_POSITION_Z
|
|
| PROP_ROTATION
|
|
)
|
|
}
|
|
|
|
pub fn is_history_choice(field: &str) -> bool {
|
|
matches!(field, PROP_HISTORY | PROP_SHOW_HISTORY)
|
|
}
|
|
|
|
pub fn apply_history_choice(
|
|
document: &mut acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
field: &str,
|
|
value: &str,
|
|
) -> bool {
|
|
let Some(graph) = document.solid_history_graph(handle) else {
|
|
return false;
|
|
};
|
|
let Some(ObjectType::DynamicBlock(object)) = document.objects.get_mut(&graph.root) else {
|
|
return false;
|
|
};
|
|
let DynamicBlockData::SolidHistory(history) = &mut object.data else {
|
|
return false;
|
|
};
|
|
match field {
|
|
PROP_HISTORY => {
|
|
history.record_history = value.eq_ignore_ascii_case("Record");
|
|
if !history.record_history {
|
|
history.show_history = false;
|
|
}
|
|
}
|
|
PROP_SHOW_HISTORY if history.record_history => {
|
|
history.show_history = value.eq_ignore_ascii_case("Yes");
|
|
}
|
|
_ => return false,
|
|
}
|
|
true
|
|
}
|
|
|
|
fn apply_box_geometry_property(
|
|
value: &mut SolidHistoryBox,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
if field == PROP_ROTATION {
|
|
let target = crate::entities::common::parse_direction(text)?;
|
|
let current = matrix(value.base.transform)?;
|
|
let center = current.transform_point3(glam::DVec3::new(
|
|
value.length * 0.5,
|
|
value.width * 0.5,
|
|
0.0,
|
|
));
|
|
let (sin, cos) = target.sin_cos();
|
|
let x_axis = glam::DVec3::new(cos, sin, 0.0);
|
|
let y_axis = glam::DVec3::new(-sin, cos, 0.0);
|
|
let origin = center - x_axis * value.length * 0.5 - y_axis * value.width * 0.5;
|
|
value.base.transform = glam::DMat4::from_cols(
|
|
x_axis.extend(0.0),
|
|
y_axis.extend(0.0),
|
|
glam::DVec3::Z.extend(0.0),
|
|
origin.extend(1.0),
|
|
)
|
|
.to_cols_array();
|
|
return Some(true);
|
|
}
|
|
let axis = match field {
|
|
PROP_POSITION_X => 0,
|
|
PROP_POSITION_Y => 1,
|
|
PROP_POSITION_Z => 2,
|
|
_ => return None,
|
|
};
|
|
let target = crate::entities::common::parse_length(text)?;
|
|
let current = matrix(value.base.transform)?;
|
|
let center = current.transform_point3(glam::DVec3::new(
|
|
value.length * 0.5,
|
|
value.width * 0.5,
|
|
0.0,
|
|
));
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - center[axis];
|
|
value.base.transform = (glam::DMat4::from_translation(delta) * current).to_cols_array();
|
|
Some(true)
|
|
}
|
|
|
|
pub fn apply_primitive_property(
|
|
operation: &mut SolidHistoryOperation,
|
|
field: &str,
|
|
value: &str,
|
|
) -> bool {
|
|
if let SolidHistoryOperation::Box(box_value) = operation {
|
|
if let Some(applied) = apply_box_geometry_property(box_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
let Some(number) = (if field == PROP_SIDES {
|
|
value.trim().parse::<f64>().ok()
|
|
} else {
|
|
crate::entities::common::parse_length(value)
|
|
}) 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) => {
|
|
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),
|
|
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> {
|
|
let matrix = glam::DMat4::from_cols_array(&transform);
|
|
(matrix.is_finite() && matrix.determinant().abs() > 1e-12).then_some(matrix)
|
|
}
|
|
|
|
fn codec_matrix(transform: &acadrust::types::Transform) -> glam::DMat4 {
|
|
let matrix = transform.matrix.m;
|
|
glam::DMat4::from_cols_array(&[
|
|
matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],
|
|
matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],
|
|
matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],
|
|
matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3],
|
|
])
|
|
}
|
|
|
|
fn transform_matrix(transform: &EntityTransform) -> Option<glam::DMat4> {
|
|
Some(match transform {
|
|
EntityTransform::Translate(delta) => glam::DMat4::from_translation(*delta),
|
|
EntityTransform::Rotate {
|
|
center,
|
|
axis,
|
|
angle_rad,
|
|
} => {
|
|
let axis = axis.normalize_or_zero();
|
|
if axis.length_squared() <= 1e-12 {
|
|
return None;
|
|
}
|
|
glam::DMat4::from_translation(*center)
|
|
* glam::DMat4::from_axis_angle(axis, *angle_rad)
|
|
* glam::DMat4::from_translation(-*center)
|
|
}
|
|
EntityTransform::Scale { center, factor } => {
|
|
glam::DMat4::from_translation(*center)
|
|
* glam::DMat4::from_scale(glam::DVec3::splat(*factor))
|
|
* glam::DMat4::from_translation(-*center)
|
|
}
|
|
EntityTransform::Mirror {
|
|
p1,
|
|
p2,
|
|
working_normal,
|
|
} => codec_matrix(&crate::scene::view::transform::reflection_about_working_line(
|
|
*p1,
|
|
*p2,
|
|
*working_normal,
|
|
)),
|
|
EntityTransform::Affine(value) => codec_matrix(value),
|
|
})
|
|
}
|
|
|
|
pub fn transform_operation(
|
|
operation: &mut SolidHistoryOperation,
|
|
transform: &EntityTransform,
|
|
) -> bool {
|
|
let Some(base) = operation.base_mut() else {
|
|
return false;
|
|
};
|
|
let Some(current) = matrix(base.transform) else {
|
|
return false;
|
|
};
|
|
let Some(by) = transform_matrix(transform) else {
|
|
return false;
|
|
};
|
|
let transformed = by * current;
|
|
if !transformed.is_finite() || transformed.determinant().abs() <= 1e-12 {
|
|
return false;
|
|
}
|
|
base.transform = transformed.to_cols_array();
|
|
true
|
|
}
|
|
|
|
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,
|
|
axis: Option<glam::DVec3>,
|
|
) -> GripDef {
|
|
GripDef {
|
|
id,
|
|
world,
|
|
is_midpoint: false,
|
|
shape,
|
|
dir: (shape == GripShape::Triangle).then_some(axis).flatten(),
|
|
axis,
|
|
}
|
|
}
|
|
|
|
pub fn primitive_grips(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
) -> Vec<GripDef> {
|
|
let Some(operation) = document.solid_history_operation(handle) else {
|
|
return Vec::new();
|
|
};
|
|
let mut grips = Vec::new();
|
|
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,
|
|
axis.and_then(|vector| world_vector(transform, vector)),
|
|
));
|
|
}
|
|
};
|
|
match operation {
|
|
SolidHistoryOperation::Box(value) => {
|
|
for corner in 0..8 {
|
|
add(
|
|
GRIP_BOX_CORNER_FIRST + corner,
|
|
value.base.transform,
|
|
[
|
|
if corner & 1 == 0 { 0.0 } else { value.length },
|
|
if corner & 2 == 0 { 0.0 } else { value.width },
|
|
if corner & 4 == 0 { 0.0 } else { value.height },
|
|
],
|
|
GripShape::Square,
|
|
None,
|
|
);
|
|
}
|
|
for (id, point, axis) in [
|
|
(
|
|
GRIP_BOX_FACE_X_MIN,
|
|
[0.0, value.width * 0.5, value.height * 0.5],
|
|
[-1.0, 0.0, 0.0],
|
|
),
|
|
(
|
|
GRIP_BOX_FACE_X_MAX,
|
|
[value.length, value.width * 0.5, value.height * 0.5],
|
|
[1.0, 0.0, 0.0],
|
|
),
|
|
(
|
|
GRIP_BOX_FACE_Y_MIN,
|
|
[value.length * 0.5, 0.0, value.height * 0.5],
|
|
[0.0, -1.0, 0.0],
|
|
),
|
|
(
|
|
GRIP_BOX_FACE_Y_MAX,
|
|
[value.length * 0.5, value.width, value.height * 0.5],
|
|
[0.0, 1.0, 0.0],
|
|
),
|
|
(
|
|
GRIP_BOX_FACE_Z_MIN,
|
|
[value.length * 0.5, value.width * 0.5, 0.0],
|
|
[0.0, 0.0, -1.0],
|
|
),
|
|
(
|
|
GRIP_BOX_FACE_Z_MAX,
|
|
[value.length * 0.5, value.width * 0.5, value.height],
|
|
[0.0, 0.0, 1.0],
|
|
),
|
|
] {
|
|
add(
|
|
id,
|
|
value.base.transform,
|
|
point,
|
|
GripShape::Triangle,
|
|
Some(axis),
|
|
);
|
|
}
|
|
}
|
|
SolidHistoryOperation::Wedge(value) => {
|
|
add(
|
|
GRIP_LENGTH,
|
|
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) => {
|
|
add(
|
|
GRIP_RADIUS,
|
|
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(
|
|
GRIP_RADIUS,
|
|
value.base.transform,
|
|
[value.radius, 0.0, 0.0],
|
|
GripShape::Square,
|
|
None,
|
|
),
|
|
SolidHistoryOperation::Torus(value) => {
|
|
add(
|
|
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) => {
|
|
add(
|
|
GRIP_RADIUS,
|
|
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(
|
|
GRIP_SIDES,
|
|
value.base.transform,
|
|
[value.radius * angle.cos(), value.radius * angle.sin(), 0.0],
|
|
GripShape::Triangle,
|
|
None,
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
grips
|
|
}
|
|
|
|
pub fn apply_primitive_grip(
|
|
operation: &mut SolidHistoryOperation,
|
|
grip_id: usize,
|
|
apply: GripApply,
|
|
) -> bool {
|
|
let GripApply::Absolute(world) = apply else {
|
|
return false;
|
|
};
|
|
let Some(transform) = operation.base().map(|base| base.transform) else {
|
|
return false;
|
|
};
|
|
let Some(local) = local_point(transform, world) else {
|
|
return false;
|
|
};
|
|
let positive = |value: f64| value.abs().max(1e-6);
|
|
match operation {
|
|
SolidHistoryOperation::Box(value) => {
|
|
let old_size = glam::DVec3::new(value.length, value.width, value.height);
|
|
let mut low = glam::DVec3::ZERO;
|
|
let mut high = old_size;
|
|
if (GRIP_BOX_CORNER_FIRST..GRIP_BOX_CORNER_FIRST + 8).contains(&grip_id) {
|
|
let corner = grip_id - GRIP_BOX_CORNER_FIRST;
|
|
for axis in 0..3 {
|
|
let opposite = if corner & (1 << axis) == 0 {
|
|
old_size[axis]
|
|
} else {
|
|
0.0
|
|
};
|
|
low[axis] = local[axis].min(opposite);
|
|
high[axis] = local[axis].max(opposite);
|
|
}
|
|
} else {
|
|
match grip_id {
|
|
GRIP_BOX_FACE_X_MIN => low.x = local.x.min(old_size.x - 1e-6),
|
|
GRIP_BOX_FACE_X_MAX => high.x = local.x.max(1e-6),
|
|
GRIP_BOX_FACE_Y_MIN => low.y = local.y.min(old_size.y - 1e-6),
|
|
GRIP_BOX_FACE_Y_MAX => high.y = local.y.max(1e-6),
|
|
GRIP_BOX_FACE_Z_MIN => low.z = local.z.min(old_size.z - 1e-6),
|
|
GRIP_BOX_FACE_Z_MAX => high.z = local.z.max(1e-6),
|
|
GRIP_LENGTH => high.x = positive(local.x),
|
|
GRIP_WIDTH => high.y = positive(local.y),
|
|
GRIP_HEIGHT => high.z = local.z.max(1e-6),
|
|
_ => return false,
|
|
}
|
|
}
|
|
let size = high - low;
|
|
if size.min_element() < 1e-6 {
|
|
return false;
|
|
}
|
|
let Some(current) = matrix(value.base.transform) else {
|
|
return false;
|
|
};
|
|
value.base.transform =
|
|
(current * glam::DMat4::from_translation(low)).to_cols_array();
|
|
value.length = size.x;
|
|
value.width = size.y;
|
|
value.height = size.z;
|
|
}
|
|
SolidHistoryOperation::Wedge(value) => {
|
|
match grip_id {
|
|
GRIP_LENGTH => value.length = positive(local.x),
|
|
GRIP_WIDTH => value.width = positive(local.y),
|
|
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
|
_ => return false,
|
|
}
|
|
}
|
|
SolidHistoryOperation::Cylinder(value) | SolidHistoryOperation::Cone(value) => {
|
|
match grip_id {
|
|
GRIP_RADIUS => {
|
|
let radius = local.x.hypot(local.y).max(1e-6);
|
|
value.major_radius = radius;
|
|
value.minor_radius = radius;
|
|
value.x_radius = radius;
|
|
}
|
|
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
|
_ => return false,
|
|
}
|
|
}
|
|
SolidHistoryOperation::Sphere(value) if grip_id == GRIP_RADIUS => {
|
|
value.radius = local.length().max(1e-6);
|
|
}
|
|
SolidHistoryOperation::Torus(value) => match grip_id {
|
|
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_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 = 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;
|
|
value.sides = value.sides.clamp(3, 71);
|
|
}
|
|
_ => return false,
|
|
},
|
|
_ => return false,
|
|
}
|
|
true
|
|
}
|
|
|
|
fn base(transform: [f64; 16]) -> SolidHistoryNodeBase {
|
|
let mut base = SolidHistoryNodeBase::new(1);
|
|
base.transform = transform;
|
|
base
|
|
}
|
|
|
|
pub fn box_op(
|
|
transform: [f64; 16],
|
|
length: f64,
|
|
width: f64,
|
|
height: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Box(SolidHistoryBox {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
length,
|
|
width,
|
|
height,
|
|
..SolidHistoryBox::default()
|
|
})
|
|
}
|
|
|
|
pub fn wedge_op(
|
|
transform: [f64; 16],
|
|
length: f64,
|
|
width: f64,
|
|
height: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Wedge(SolidHistoryBox {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
length,
|
|
width,
|
|
height,
|
|
..SolidHistoryBox::default()
|
|
})
|
|
}
|
|
|
|
pub fn cylinder_op(
|
|
transform: [f64; 16],
|
|
radius: f64,
|
|
height: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Cylinder(SolidHistoryCylinder {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
height,
|
|
major_radius: radius,
|
|
minor_radius: radius,
|
|
x_radius: radius,
|
|
..SolidHistoryCylinder::default()
|
|
})
|
|
}
|
|
|
|
pub fn cone_op(
|
|
transform: [f64; 16],
|
|
radius: f64,
|
|
height: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Cone(SolidHistoryCylinder {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
height,
|
|
major_radius: radius,
|
|
minor_radius: radius,
|
|
x_radius: radius,
|
|
..SolidHistoryCylinder::default()
|
|
})
|
|
}
|
|
|
|
pub fn sphere_op(transform: [f64; 16], radius: f64) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Sphere(SolidHistorySphere {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
radius,
|
|
..SolidHistorySphere::default()
|
|
})
|
|
}
|
|
|
|
pub fn torus_op(
|
|
transform: [f64; 16],
|
|
major_radius: f64,
|
|
minor_radius: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Torus(SolidHistoryTorus {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
major_radius,
|
|
minor_radius,
|
|
..SolidHistoryTorus::default()
|
|
})
|
|
}
|
|
|
|
pub fn pyramid_op(
|
|
transform: [f64; 16],
|
|
radius: f64,
|
|
height: f64,
|
|
sides: usize,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Pyramid(SolidHistoryPyramid {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
height,
|
|
sides: sides as i32,
|
|
radius,
|
|
..SolidHistoryPyramid::default()
|
|
})
|
|
}
|
|
|
|
pub fn brep_op(body: &Body) -> SolidHistoryOperation {
|
|
let acis_data = crate::scene::convert::acis_export::solid_to_sat(body)
|
|
.map(|document| {
|
|
let mut solid = Solid3D::new();
|
|
solid.set_sat_document(&document);
|
|
solid.acis_data
|
|
})
|
|
.unwrap_or_default();
|
|
SolidHistoryOperation::Brep(SolidHistoryBrep {
|
|
base: base(glam::DMat4::IDENTITY.to_cols_array()),
|
|
operation_major: 1,
|
|
acis_data,
|
|
..SolidHistoryBrep::default()
|
|
})
|
|
}
|