forked from 1b-agent/opencadstudio-upstream
3215 lines
112 KiB
Rust
3215 lines
112 KiB
Rust
use acadrust::entities::{EmbeddedEntity, Solid3D};
|
|
use acadrust::objects::{
|
|
DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone,
|
|
SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation,
|
|
SolidHistoryPyramid, SolidHistoryRevolve, SolidHistorySphere, SolidHistorySweep,
|
|
SolidHistoryTorus,
|
|
};
|
|
use acadrust::EntityType;
|
|
use cadkernel::brep::Body;
|
|
|
|
use crate::command::EntityTransform;
|
|
use crate::entities::traits::EntityTypeOps;
|
|
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_MAJOR_RADIUS: usize = 10_008;
|
|
pub const GRIP_MINOR_RADIUS: usize = 10_009;
|
|
pub const GRIP_TOP_RADIUS: usize = 10_010;
|
|
pub const GRIP_DRAFT: usize = 10_012;
|
|
pub const GRIP_PROFILE_FIRST: usize = 10_200;
|
|
pub const GRIP_SWEEP_PROFILE_FIRST: usize = 20_000;
|
|
pub const GRIP_SWEEP_PATH_FIRST: usize = 30_000;
|
|
pub const GRIP_LOFT_SECTION_FIRST: usize = 40_000;
|
|
pub const GRIP_REVOLVE_PROFILE_FIRST: usize = 50_000;
|
|
pub const GRIP_REVOLVE_AXIS: usize = 10_013;
|
|
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_DIAMETER: &str = "solid_history_diameter";
|
|
pub const PROP_BASE_RADIUS: &str = "solid_history_base_radius";
|
|
pub const PROP_TOP_RADIUS: &str = "solid_history_top_radius";
|
|
pub const PROP_BASE_MAJOR_RADIUS: &str = "solid_history_base_major_radius";
|
|
pub const PROP_BASE_MINOR_RADIUS: &str = "solid_history_base_minor_radius";
|
|
pub const PROP_TOP_MAJOR_RADIUS: &str = "solid_history_top_major_radius";
|
|
pub const PROP_TOP_MINOR_RADIUS: &str = "solid_history_top_minor_radius";
|
|
pub const PROP_MAJOR_RADIUS: &str = "solid_history_major_radius";
|
|
pub const PROP_MINOR_RADIUS: &str = "solid_history_minor_radius";
|
|
pub const PROP_ELLIPTICAL: &str = "solid_history_elliptical";
|
|
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_PROFILE_ROTATION: &str = "solid_history_profile_rotation";
|
|
pub const PROP_BANK: &str = "solid_history_bank";
|
|
pub const PROP_TWIST_ALONG_PATH: &str = "solid_history_twist_along_path";
|
|
pub const PROP_SCALE_ALONG_PATH: &str = "solid_history_scale_along_path";
|
|
pub const PROP_SWEEP_LENGTH: &str = "solid_history_sweep_length";
|
|
pub const PROP_EXTRUSION_HEIGHT: &str = "solid_history_extrusion_height";
|
|
pub const PROP_TAPER_ANGLE: &str = "solid_history_taper_angle";
|
|
pub const PROP_REVOLVE_ANGLE: &str = "solid_history_revolve_angle";
|
|
pub const PROP_AXIS_POSITION_X: &str = "solid_history_axis_position_x";
|
|
pub const PROP_AXIS_POSITION_Y: &str = "solid_history_axis_position_y";
|
|
pub const PROP_AXIS_POSITION_Z: &str = "solid_history_axis_position_z";
|
|
pub const PROP_AXIS_DIRECTION_X: &str = "solid_history_axis_direction_x";
|
|
pub const PROP_AXIS_DIRECTION_Y: &str = "solid_history_axis_direction_y";
|
|
pub const PROP_AXIS_DIRECTION_Z: &str = "solid_history_axis_direction_z";
|
|
pub const PROP_PYRAMID_TYPE: &str = "solid_history_pyramid_type";
|
|
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, i16)> {
|
|
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,
|
|
document.header.show_solid_history.clamp(0, 2),
|
|
))
|
|
}
|
|
|
|
fn displayed_history_state(object_show_history: bool, show_history_mode: i16) -> (bool, bool) {
|
|
match show_history_mode {
|
|
0 => (false, false),
|
|
2 => (true, false),
|
|
_ => (object_show_history, true),
|
|
}
|
|
}
|
|
|
|
pub fn has_specialized_primitive_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
) -> bool {
|
|
matches!(
|
|
document.solid_history_operation(handle),
|
|
Some(
|
|
SolidHistoryOperation::Box(_)
|
|
| SolidHistoryOperation::Wedge(_)
|
|
| SolidHistoryOperation::Sphere(_)
|
|
| SolidHistoryOperation::Cone(_)
|
|
| SolidHistoryOperation::Cylinder(_)
|
|
| SolidHistoryOperation::Torus(_)
|
|
| SolidHistoryOperation::Pyramid(_)
|
|
| SolidHistoryOperation::Sweep(_)
|
|
| SolidHistoryOperation::Extrusion(_)
|
|
| SolidHistoryOperation::Revolve(_)
|
|
)
|
|
)
|
|
}
|
|
|
|
pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3> {
|
|
match operation {
|
|
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => world_point(
|
|
value.base.transform,
|
|
[value.length * 0.5, value.width * 0.5, 0.0],
|
|
),
|
|
SolidHistoryOperation::Sphere(value) => {
|
|
world_point(value.base.transform, [0.0, 0.0, 0.0])
|
|
}
|
|
SolidHistoryOperation::Cone(value) => world_point(value.base.transform, [0.0; 3]),
|
|
SolidHistoryOperation::Cylinder(value) => world_point(value.base.transform, [0.0; 3]),
|
|
SolidHistoryOperation::Sweep(value) | SolidHistoryOperation::Extrusion(value) => world_point(
|
|
value.base.transform,
|
|
[
|
|
value.reference_point.x,
|
|
value.reference_point.y,
|
|
value.reference_point.z,
|
|
],
|
|
),
|
|
SolidHistoryOperation::Torus(value) => world_point(value.base.transform, [0.0; 3]),
|
|
SolidHistoryOperation::Pyramid(value) => world_point(value.base.transform, [0.0; 3]),
|
|
SolidHistoryOperation::Revolve(value) => world_point(
|
|
value.base.transform,
|
|
[value.axis_point.x, value.axis_point.y, value.axis_point.z],
|
|
),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn revolve_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryRevolve,
|
|
) -> Vec<PropSection> {
|
|
let Some(axis_position) = world_point(
|
|
value.base.transform,
|
|
[value.axis_point.x, value.axis_point.y, value.axis_point.z],
|
|
) else {
|
|
return Vec::new();
|
|
};
|
|
let Some(axis_direction) = world_vector(
|
|
value.base.transform,
|
|
[value.direction.x, value.direction.y, value.direction.z],
|
|
) else {
|
|
return Vec::new();
|
|
};
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
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!("Revolve").into_owned()),
|
|
},
|
|
Property {
|
|
label: t!("Angle of revolution").into_owned(),
|
|
field: PROP_REVOLVE_ANGLE,
|
|
value: PropValue::EditText(crate::entities::common::format_angle(
|
|
value.revolve_angle,
|
|
)),
|
|
},
|
|
crate::entities::common::edit_prop(
|
|
t!("Axis position X").as_ref(),
|
|
PROP_AXIS_POSITION_X,
|
|
axis_position.x,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Axis position Y").as_ref(),
|
|
PROP_AXIS_POSITION_Y,
|
|
axis_position.y,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Axis position Z").as_ref(),
|
|
PROP_AXIS_POSITION_Z,
|
|
axis_position.z,
|
|
),
|
|
crate::entities::common::edit_scalar_prop(
|
|
t!("Axis direction X").as_ref(),
|
|
PROP_AXIS_DIRECTION_X,
|
|
axis_direction.x,
|
|
),
|
|
crate::entities::common::edit_scalar_prop(
|
|
t!("Axis direction Y").as_ref(),
|
|
PROP_AXIS_DIRECTION_Y,
|
|
axis_direction.y,
|
|
),
|
|
crate::entities::common::edit_scalar_prop(
|
|
t!("Axis direction Z").as_ref(),
|
|
PROP_AXIS_DIRECTION_Z,
|
|
axis_direction.z,
|
|
),
|
|
],
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_value.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn extrusion_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistorySweep,
|
|
) -> Vec<PropSection> {
|
|
let position = world_point(
|
|
value.base.transform,
|
|
[
|
|
value.reference_point.x,
|
|
value.reference_point.y,
|
|
value.reference_point.z,
|
|
],
|
|
)
|
|
.unwrap_or(glam::DVec3::ZERO);
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
let path_length = sweep_path_length(value);
|
|
let height_value = path_length.unwrap_or(value.end_draft_distance);
|
|
let height = if value.path_entity.is_some() {
|
|
PropValue::ReadOnly(crate::entities::common::format_length(height_value))
|
|
} else {
|
|
PropValue::EditText(crate::entities::common::format_length(height_value))
|
|
};
|
|
let taper = if value.path_entity.is_some() {
|
|
PropValue::ReadOnly(crate::entities::common::format_angle(value.draft_angle))
|
|
} else {
|
|
PropValue::EditText(crate::entities::common::format_angle(value.draft_angle))
|
|
};
|
|
vec![
|
|
PropSection {
|
|
title: t!("Geometry").into_owned(),
|
|
props: vec![
|
|
Property {
|
|
label: t!("Solid type").into_owned(),
|
|
field: "solid_history_type",
|
|
value: PropValue::ReadOnly(t!("Extrusion").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,
|
|
),
|
|
Property {
|
|
label: t!("Height").into_owned(),
|
|
field: PROP_EXTRUSION_HEIGHT,
|
|
value: height,
|
|
},
|
|
Property {
|
|
label: t!("Taper angle").into_owned(),
|
|
field: PROP_TAPER_ANGLE,
|
|
value: taper,
|
|
},
|
|
],
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: if show_history { "Yes" } else { "No" }.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(if show_history { "Yes" } else { "No" }.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn sweep_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistorySweep,
|
|
) -> Vec<PropSection> {
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
let show_value = if show_history { "Yes" } else { "No" };
|
|
let length = sweep_path_length(value)
|
|
.map(crate::entities::common::format_length)
|
|
.unwrap_or_default();
|
|
vec![
|
|
PropSection {
|
|
title: t!("Geometry").into_owned(),
|
|
props: vec![
|
|
Property {
|
|
label: t!("Solid type").into_owned(),
|
|
field: "solid_history_type",
|
|
value: PropValue::ReadOnly(t!("Sweep").into_owned()),
|
|
},
|
|
Property {
|
|
label: t!("Profile rotation").into_owned(),
|
|
field: PROP_PROFILE_ROTATION,
|
|
value: PropValue::EditText(
|
|
crate::entities::common::format_angle(value.align_angle),
|
|
),
|
|
},
|
|
Property {
|
|
label: t!("Bank").into_owned(),
|
|
field: PROP_BANK,
|
|
value: PropValue::ReadOnly(
|
|
if value.bank { "Yes" } else { "No" }.to_string(),
|
|
),
|
|
},
|
|
Property {
|
|
label: t!("Twist along path").into_owned(),
|
|
field: PROP_TWIST_ALONG_PATH,
|
|
value: PropValue::EditText(
|
|
crate::entities::common::format_angle(value.twist_angle),
|
|
),
|
|
},
|
|
Property {
|
|
label: t!("Scale along path").into_owned(),
|
|
field: PROP_SCALE_ALONG_PATH,
|
|
value: PropValue::EditText(value.scale_factor.to_string()),
|
|
},
|
|
Property {
|
|
label: t!("Length").into_owned(),
|
|
field: PROP_SWEEP_LENGTH,
|
|
value: PropValue::ReadOnly(length),
|
|
},
|
|
],
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_value.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn sweep_path_length(value: &SolidHistorySweep) -> Option<f64> {
|
|
use acadrust::entities::EmbeddedEntity;
|
|
use acadrust::EntityType;
|
|
|
|
let path_entity = value.path_entity.as_ref()?;
|
|
if let EmbeddedEntity::Spline(path) = path_entity {
|
|
if path.degree == 1 && path.control_points.len() >= 2 && path.weights.is_empty() {
|
|
let transform = glam::DMat4::from_cols_array(&value.path_entity_transform);
|
|
if !transform.is_finite() || transform.determinant().abs() <= 1e-12 {
|
|
return None;
|
|
}
|
|
return Some(
|
|
path.control_points
|
|
.windows(2)
|
|
.map(|pair| {
|
|
let start = transform.transform_point3(glam::DVec3::new(
|
|
pair[0].x, pair[0].y, pair[0].z,
|
|
));
|
|
let end = transform.transform_point3(glam::DVec3::new(
|
|
pair[1].x, pair[1].y, pair[1].z,
|
|
));
|
|
end.distance(start)
|
|
})
|
|
.sum(),
|
|
);
|
|
}
|
|
}
|
|
let entity = match path_entity {
|
|
EmbeddedEntity::Line(value) => EntityType::Line(value.clone()),
|
|
EmbeddedEntity::Arc(value) => EntityType::Arc(value.clone()),
|
|
EmbeddedEntity::Circle(value) => EntityType::Circle(value.clone()),
|
|
EmbeddedEntity::Ellipse(value) => EntityType::Ellipse(value.clone()),
|
|
EmbeddedEntity::Spline(value) => EntityType::Spline(value.clone()),
|
|
EmbeddedEntity::LwPolyline(value) => EntityType::LwPolyline(value.clone()),
|
|
_ => return None,
|
|
};
|
|
let curve = crate::entities::curve::entity_curve(&entity)?;
|
|
let transform = value.path_entity_transform;
|
|
if transform.iter().any(|value| !value.is_finite())
|
|
|| transform[3].abs() > 1e-9
|
|
|| transform[7].abs() > 1e-9
|
|
|| transform[11].abs() > 1e-9
|
|
|| (transform[15] - 1.0).abs() > 1e-9
|
|
{
|
|
return None;
|
|
}
|
|
let placement = cadkernel::brep::Placement {
|
|
x_axis: [transform[0], transform[1], transform[2]],
|
|
y_axis: [transform[4], transform[5], transform[6]],
|
|
z_axis: [transform[8], transform[9], transform[10]],
|
|
origin: [transform[12], transform[13], transform[14]],
|
|
};
|
|
let length = curve.curve.length() * placement.scale()?;
|
|
(length.is_finite() && length >= 0.0).then_some(length)
|
|
}
|
|
|
|
fn torus_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryTorus,
|
|
) -> Vec<PropSection> {
|
|
let Some(position) = world_point(value.base.transform, [0.0; 3]) else {
|
|
return Vec::new();
|
|
};
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, _) = displayed_history_state(object_show_history, show_history_mode);
|
|
vec![
|
|
PropSection {
|
|
title: t!("Geometry").into_owned(),
|
|
props: vec![
|
|
Property {
|
|
label: t!("Solid type").into_owned(),
|
|
field: "solid_history_type",
|
|
value: PropValue::ReadOnly(t!("Torus").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,
|
|
),
|
|
history_prop(
|
|
t!("Torus radius").as_ref(),
|
|
PROP_MAJOR_RADIUS,
|
|
value.major_radius,
|
|
),
|
|
history_prop(
|
|
t!("Tube radius").as_ref(),
|
|
PROP_MINOR_RADIUS,
|
|
value.minor_radius,
|
|
),
|
|
],
|
|
},
|
|
PropSection {
|
|
title: t!("Solid History").into_owned(),
|
|
props: vec![
|
|
Property {
|
|
label: t!("History").into_owned(),
|
|
field: PROP_HISTORY,
|
|
value: PropValue::ReadOnly(
|
|
if record_history { "Record" } else { "None" }.to_string(),
|
|
),
|
|
},
|
|
Property {
|
|
label: t!("Show History").into_owned(),
|
|
field: PROP_SHOW_HISTORY,
|
|
value: PropValue::ReadOnly(
|
|
if show_history { "Yes" } else { "No" }.to_string(),
|
|
),
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn cone_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryCone,
|
|
) -> Vec<PropSection> {
|
|
let Some(position) = world_point(value.base.transform, [0.0; 3]) else {
|
|
return Vec::new();
|
|
};
|
|
let scale = value
|
|
.base_x_radius
|
|
.abs()
|
|
.max(value.base_y_radius.abs())
|
|
.max(1.0);
|
|
let elliptical = (value.base_x_radius - value.base_y_radius).abs() > 1e-9 * scale;
|
|
let rotation = matrix(value.base.transform)
|
|
.map(|matrix| matrix.x_axis.y.atan2(matrix.x_axis.x))
|
|
.unwrap_or(0.0);
|
|
let top_minor_radius = if value.base_x_radius.abs() > 1e-9 {
|
|
value.top_radius * value.base_y_radius / value.base_x_radius
|
|
} else {
|
|
0.0
|
|
};
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
let show_value = if show_history { "Yes" } else { "No" };
|
|
let mut geometry = vec![
|
|
Property {
|
|
label: t!("Solid type").into_owned(),
|
|
field: "solid_history_type",
|
|
value: PropValue::ReadOnly(t!("Cone").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,
|
|
),
|
|
Property {
|
|
label: t!("Elliptical").into_owned(),
|
|
field: PROP_ELLIPTICAL,
|
|
value: PropValue::ReadOnly(if elliptical { "Yes" } else { "No" }.to_string()),
|
|
},
|
|
];
|
|
if elliptical {
|
|
geometry.extend([
|
|
history_prop(
|
|
t!("Base major radius").as_ref(),
|
|
PROP_BASE_MAJOR_RADIUS,
|
|
value.base_x_radius,
|
|
),
|
|
history_prop(
|
|
t!("Base minor radius").as_ref(),
|
|
PROP_BASE_MINOR_RADIUS,
|
|
value.base_y_radius,
|
|
),
|
|
history_prop(
|
|
t!("Top major radius").as_ref(),
|
|
PROP_TOP_MAJOR_RADIUS,
|
|
value.top_radius,
|
|
),
|
|
history_prop(
|
|
t!("Top minor radius").as_ref(),
|
|
PROP_TOP_MINOR_RADIUS,
|
|
top_minor_radius,
|
|
),
|
|
Property {
|
|
label: t!("Rotation").into_owned(),
|
|
field: PROP_ROTATION,
|
|
value: PropValue::ReadOnly(crate::entities::common::format_direction(rotation)),
|
|
},
|
|
]);
|
|
} else {
|
|
geometry.extend([
|
|
history_prop(
|
|
t!("Base radius").as_ref(),
|
|
PROP_BASE_RADIUS,
|
|
value.base_x_radius,
|
|
),
|
|
history_prop(
|
|
t!("Top radius").as_ref(),
|
|
PROP_TOP_RADIUS,
|
|
value.top_radius,
|
|
),
|
|
]);
|
|
}
|
|
geometry.push(history_prop(
|
|
t!("Height").as_ref(),
|
|
PROP_HEIGHT,
|
|
value.height,
|
|
));
|
|
vec![
|
|
PropSection {
|
|
title: t!("Geometry").into_owned(),
|
|
props: geometry,
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_value.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn cylinder_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryCylinder,
|
|
) -> Vec<PropSection> {
|
|
let Some(position) = world_point(value.base.transform, [0.0; 3]) else {
|
|
return Vec::new();
|
|
};
|
|
let scale = value
|
|
.major_radius
|
|
.abs()
|
|
.max(value.minor_radius.abs())
|
|
.max(1.0);
|
|
let elliptical = (value.major_radius - value.minor_radius).abs() > 1e-9 * scale;
|
|
let rotation = matrix(value.base.transform)
|
|
.map(|matrix| matrix.x_axis.y.atan2(matrix.x_axis.x))
|
|
.unwrap_or(0.0);
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
let show_value = if show_history { "Yes" } else { "No" };
|
|
let mut geometry = vec![
|
|
Property {
|
|
label: t!("Solid type").into_owned(),
|
|
field: "solid_history_type",
|
|
value: PropValue::ReadOnly(t!("Cylinder").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,
|
|
),
|
|
Property {
|
|
label: t!("Elliptical").into_owned(),
|
|
field: PROP_ELLIPTICAL,
|
|
value: PropValue::ReadOnly(if elliptical { "Yes" } else { "No" }.to_string()),
|
|
},
|
|
];
|
|
if elliptical {
|
|
geometry.extend([
|
|
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,
|
|
),
|
|
Property {
|
|
label: t!("Rotation").into_owned(),
|
|
field: PROP_ROTATION,
|
|
value: PropValue::ReadOnly(crate::entities::common::format_direction(rotation)),
|
|
},
|
|
]);
|
|
} else {
|
|
geometry.push(history_prop(
|
|
t!("Radius").as_ref(),
|
|
PROP_RADIUS,
|
|
value.major_radius,
|
|
));
|
|
}
|
|
geometry.push(history_prop(
|
|
t!("Height").as_ref(),
|
|
PROP_HEIGHT,
|
|
value.height,
|
|
));
|
|
vec![
|
|
PropSection {
|
|
title: t!("Geometry").into_owned(),
|
|
props: geometry,
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_value.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn pyramid_is_inscribed(value: &SolidHistoryPyramid) -> bool {
|
|
value.operation_minor == -1
|
|
}
|
|
|
|
fn pyramid_apothem_factor(sides: i32) -> f64 {
|
|
(std::f64::consts::PI / sides.clamp(3, 32) as f64).cos()
|
|
}
|
|
|
|
fn pyramid_display_radius(value: &SolidHistoryPyramid, radius: f64) -> f64 {
|
|
if pyramid_is_inscribed(value) {
|
|
radius
|
|
} else {
|
|
radius * pyramid_apothem_factor(value.sides)
|
|
}
|
|
}
|
|
|
|
fn pyramid_display_rotation(value: &SolidHistoryPyramid) -> Option<f64> {
|
|
let vertex_rotation = matrix(value.base.transform)
|
|
.map(|matrix| matrix.x_axis.y.atan2(matrix.x_axis.x))?;
|
|
if pyramid_is_inscribed(value) {
|
|
Some(vertex_rotation)
|
|
} else {
|
|
Some(vertex_rotation + std::f64::consts::PI / value.sides.clamp(3, 32) as f64)
|
|
}
|
|
}
|
|
|
|
fn pyramid_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryPyramid,
|
|
) -> Vec<PropSection> {
|
|
let Some(position) = world_point(value.base.transform, [0.0; 3]) else {
|
|
return Vec::new();
|
|
};
|
|
let rotation = pyramid_display_rotation(value).unwrap_or(0.0);
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
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!("Pyramid").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,
|
|
),
|
|
Property {
|
|
label: t!("Type").into_owned(),
|
|
field: PROP_PYRAMID_TYPE,
|
|
value: PropValue::Choice {
|
|
selected: if pyramid_is_inscribed(value) {
|
|
"Inscribed"
|
|
} else {
|
|
"Circumscribed"
|
|
}
|
|
.to_string(),
|
|
options: vec!["Circumscribed".to_string(), "Inscribed".to_string()],
|
|
},
|
|
},
|
|
history_prop(
|
|
t!("Base radius").as_ref(),
|
|
PROP_BASE_RADIUS,
|
|
pyramid_display_radius(value, value.radius),
|
|
),
|
|
history_prop(
|
|
t!("Top radius").as_ref(),
|
|
PROP_TOP_RADIUS,
|
|
pyramid_display_radius(value, value.top_radius),
|
|
),
|
|
history_prop(t!("Sides").as_ref(), PROP_SIDES, value.sides),
|
|
Property {
|
|
label: t!("Rotation").into_owned(),
|
|
field: PROP_ROTATION,
|
|
value: PropValue::EditText(
|
|
crate::entities::common::format_direction(rotation),
|
|
),
|
|
},
|
|
history_prop(t!("Height").as_ref(), PROP_HEIGHT, value.height),
|
|
],
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_value.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn sphere_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistorySphere,
|
|
) -> Vec<PropSection> {
|
|
let Some(position) = world_point(value.base.transform, [0.0; 3]) else {
|
|
return Vec::new();
|
|
};
|
|
let (record_history, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
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!("Sphere").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!("Radius").as_ref(),
|
|
PROP_RADIUS,
|
|
value.radius,
|
|
),
|
|
crate::entities::common::edit_prop(
|
|
t!("Diameter").as_ref(),
|
|
PROP_DIAMETER,
|
|
value.radius * 2.0,
|
|
),
|
|
],
|
|
},
|
|
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 show_history_editable {
|
|
PropValue::Choice {
|
|
selected: show_value.to_string(),
|
|
options: vec!["No".to_string(), "Yes".to_string()],
|
|
}
|
|
} else {
|
|
PropValue::ReadOnly(show_value.to_string())
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn rectangular_properties(
|
|
document: &acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
value: &SolidHistoryBox,
|
|
solid_type: &str,
|
|
) -> 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, object_show_history, show_history_mode) =
|
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
|
let (show_history, show_history_editable) =
|
|
displayed_history_state(object_show_history, show_history_mode);
|
|
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!(solid_type).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 show_history_editable {
|
|
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();
|
|
};
|
|
match operation {
|
|
SolidHistoryOperation::Box(value) => {
|
|
rectangular_properties(document, handle, value, "Box")
|
|
}
|
|
SolidHistoryOperation::Wedge(value) => {
|
|
rectangular_properties(document, handle, value, "Wedge")
|
|
}
|
|
SolidHistoryOperation::Sphere(value) => sphere_properties(document, handle, value),
|
|
SolidHistoryOperation::Cone(value) => cone_properties(document, handle, value),
|
|
SolidHistoryOperation::Cylinder(value) => cylinder_properties(document, handle, value),
|
|
SolidHistoryOperation::Pyramid(value) => pyramid_properties(document, handle, value),
|
|
SolidHistoryOperation::Torus(value) => torus_properties(document, handle, value),
|
|
SolidHistoryOperation::Sweep(value) => sweep_properties(document, handle, value),
|
|
SolidHistoryOperation::Extrusion(value) => {
|
|
extrusion_properties(document, handle, value)
|
|
}
|
|
SolidHistoryOperation::Revolve(value) => revolve_properties(document, handle, value),
|
|
_ => Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn is_primitive_property(field: &str) -> bool {
|
|
matches!(
|
|
field,
|
|
PROP_LENGTH
|
|
| PROP_WIDTH
|
|
| PROP_HEIGHT
|
|
| PROP_RADIUS
|
|
| PROP_DIAMETER
|
|
| PROP_BASE_RADIUS
|
|
| PROP_TOP_RADIUS
|
|
| PROP_BASE_MAJOR_RADIUS
|
|
| PROP_BASE_MINOR_RADIUS
|
|
| PROP_TOP_MAJOR_RADIUS
|
|
| PROP_TOP_MINOR_RADIUS
|
|
| PROP_MAJOR_RADIUS
|
|
| PROP_MINOR_RADIUS
|
|
| PROP_OUTER_RADIUS
|
|
| PROP_INNER_RADIUS
|
|
| PROP_SIDES
|
|
| PROP_POSITION_X
|
|
| PROP_POSITION_Y
|
|
| PROP_POSITION_Z
|
|
| PROP_ROTATION
|
|
| PROP_PROFILE_ROTATION
|
|
| PROP_TWIST_ALONG_PATH
|
|
| PROP_SCALE_ALONG_PATH
|
|
| PROP_EXTRUSION_HEIGHT
|
|
| PROP_TAPER_ANGLE
|
|
| PROP_REVOLVE_ANGLE
|
|
| PROP_AXIS_POSITION_X
|
|
| PROP_AXIS_POSITION_Y
|
|
| PROP_AXIS_POSITION_Z
|
|
| PROP_AXIS_DIRECTION_X
|
|
| PROP_AXIS_DIRECTION_Y
|
|
| PROP_AXIS_DIRECTION_Z
|
|
| PROP_PYRAMID_TYPE
|
|
)
|
|
}
|
|
|
|
pub fn is_history_choice(field: &str) -> bool {
|
|
matches!(field, PROP_HISTORY | PROP_SHOW_HISTORY)
|
|
}
|
|
|
|
pub fn is_specialized_property(field: &str) -> bool {
|
|
matches!(field, "solid_history_type" | PROP_BANK | PROP_SWEEP_LENGTH)
|
|
|| is_primitive_property(field)
|
|
|| is_history_choice(field)
|
|
}
|
|
|
|
fn apply_extrusion_geometry_property(
|
|
value: &mut SolidHistorySweep,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
if value.path_entity.is_some()
|
|
&& matches!(field, PROP_EXTRUSION_HEIGHT | PROP_TAPER_ANGLE)
|
|
{
|
|
return Some(false);
|
|
}
|
|
match field {
|
|
PROP_POSITION_X | PROP_POSITION_Y | PROP_POSITION_Z => {
|
|
let axis = match field {
|
|
PROP_POSITION_X => 0,
|
|
PROP_POSITION_Y => 1,
|
|
_ => 2,
|
|
};
|
|
let target = crate::entities::common::parse_length(text)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let reference = current.transform_point3(glam::DVec3::new(
|
|
value.reference_point.x,
|
|
value.reference_point.y,
|
|
value.reference_point.z,
|
|
));
|
|
if !reference.is_finite() || (target - reference[axis]).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - reference[axis];
|
|
let updated = glam::DMat4::from_translation(delta) * current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.to_cols_array();
|
|
Some(true)
|
|
}
|
|
PROP_EXTRUSION_HEIGHT => {
|
|
let height = crate::entities::common::parse_length(text)?;
|
|
if !height.is_finite() || height <= 1e-9 {
|
|
return Some(false);
|
|
}
|
|
let direction = glam::DVec3::new(
|
|
value.direction.x,
|
|
value.direction.y,
|
|
value.direction.z,
|
|
);
|
|
let Some(unit) = direction.try_normalize() else {
|
|
return Some(false);
|
|
};
|
|
let updated = unit * height;
|
|
if (updated - direction).length_squared() <= 1e-24 {
|
|
return Some(false);
|
|
}
|
|
value.direction = acadrust::types::Vector3::new(updated.x, updated.y, updated.z);
|
|
value.end_draft_distance = height;
|
|
Some(true)
|
|
}
|
|
PROP_TAPER_ANGLE => {
|
|
let angle = crate::entities::common::parse_angle(text)?;
|
|
if !angle.is_finite()
|
|
|| angle.abs() >= std::f64::consts::FRAC_PI_2
|
|
|| (angle - value.draft_angle).abs() <= 1e-12
|
|
{
|
|
return Some(false);
|
|
}
|
|
value.draft_angle = angle;
|
|
Some(true)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn apply_revolve_geometry_property(
|
|
value: &mut SolidHistoryRevolve,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
if field == PROP_REVOLVE_ANGLE {
|
|
let angle = crate::entities::common::parse_angle(text)?;
|
|
if !angle.is_finite()
|
|
|| angle.abs() <= 1e-12
|
|
|| angle.abs() > std::f64::consts::TAU + 1e-12
|
|
|| (angle - value.revolve_angle).abs() <= 1e-12
|
|
{
|
|
return Some(false);
|
|
}
|
|
value.revolve_angle = angle;
|
|
return Some(true);
|
|
}
|
|
|
|
if matches!(
|
|
field,
|
|
PROP_AXIS_POSITION_X | PROP_AXIS_POSITION_Y | PROP_AXIS_POSITION_Z
|
|
) {
|
|
let axis = match field {
|
|
PROP_AXIS_POSITION_X => 0,
|
|
PROP_AXIS_POSITION_Y => 1,
|
|
_ => 2,
|
|
};
|
|
let target = crate::entities::common::parse_length(text)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let mut world = world_point(
|
|
value.base.transform,
|
|
[value.axis_point.x, value.axis_point.y, value.axis_point.z],
|
|
)?;
|
|
if (world[axis] - target).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
world[axis] = target;
|
|
let local = local_point(value.base.transform, world)?;
|
|
if !local.is_finite() {
|
|
return Some(false);
|
|
}
|
|
value.axis_point = acadrust::types::Vector3::new(local.x, local.y, local.z);
|
|
return Some(true);
|
|
}
|
|
|
|
if matches!(
|
|
field,
|
|
PROP_AXIS_DIRECTION_X | PROP_AXIS_DIRECTION_Y | PROP_AXIS_DIRECTION_Z
|
|
) {
|
|
let axis = match field {
|
|
PROP_AXIS_DIRECTION_X => 0,
|
|
PROP_AXIS_DIRECTION_Y => 1,
|
|
_ => 2,
|
|
};
|
|
let target = text.trim().replace(',', ".").parse::<f64>().ok()?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let mut world = world_vector(
|
|
value.base.transform,
|
|
[value.direction.x, value.direction.y, value.direction.z],
|
|
)?;
|
|
world[axis] = target;
|
|
let Some(world) = world.try_normalize() else {
|
|
return Some(false);
|
|
};
|
|
let Some(local) = matrix(value.base.transform)?
|
|
.inverse()
|
|
.transform_vector3(world)
|
|
.try_normalize()
|
|
else {
|
|
return Some(false);
|
|
};
|
|
let current = glam::DVec3::new(value.direction.x, value.direction.y, value.direction.z)
|
|
.normalize_or_zero();
|
|
if (local - current).length_squared() <= 1e-24 {
|
|
return Some(false);
|
|
}
|
|
value.direction = acadrust::types::Vector3::new(local.x, local.y, local.z);
|
|
return Some(true);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn apply_history_choice(
|
|
document: &mut acadrust::CadDocument,
|
|
handle: acadrust::Handle,
|
|
field: &str,
|
|
value: &str,
|
|
) -> bool {
|
|
let show_history_mode = document.header.show_solid_history.clamp(0, 2);
|
|
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;
|
|
};
|
|
let before = (history.record_history, history.show_history);
|
|
match field {
|
|
PROP_HISTORY => {
|
|
history.record_history = if value.eq_ignore_ascii_case("Record") {
|
|
true
|
|
} else if value.eq_ignore_ascii_case("None") {
|
|
false
|
|
} else {
|
|
return false;
|
|
};
|
|
}
|
|
PROP_SHOW_HISTORY if show_history_mode == 1 => {
|
|
history.show_history = if value.eq_ignore_ascii_case("Yes") {
|
|
true
|
|
} else if value.eq_ignore_ascii_case("No") {
|
|
false
|
|
} else {
|
|
return false;
|
|
};
|
|
}
|
|
_ => return false,
|
|
}
|
|
before != (history.record_history, history.show_history)
|
|
}
|
|
|
|
fn apply_rectangular_geometry_property(
|
|
value: &mut SolidHistoryBox,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
if field == PROP_ROTATION {
|
|
let target = crate::entities::common::parse_direction(text)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let center = current.transform_point3(glam::DVec3::new(
|
|
value.length * 0.5,
|
|
value.width * 0.5,
|
|
0.0,
|
|
));
|
|
if !center.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let projected = current.x_axis.truncate();
|
|
if projected.length_squared() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let current_angle = projected.y.atan2(projected.x);
|
|
let delta = (target - current_angle + std::f64::consts::PI)
|
|
.rem_euclid(std::f64::consts::TAU)
|
|
- std::f64::consts::PI;
|
|
if delta.abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let updated = glam::DMat4::from_translation(center)
|
|
* glam::DMat4::from_rotation_z(delta)
|
|
* glam::DMat4::from_translation(-center)
|
|
* current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.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)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let center = current.transform_point3(glam::DVec3::new(
|
|
value.length * 0.5,
|
|
value.width * 0.5,
|
|
0.0,
|
|
));
|
|
if !center.is_finite() {
|
|
return Some(false);
|
|
}
|
|
if target == center[axis] {
|
|
return Some(false);
|
|
}
|
|
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)
|
|
}
|
|
|
|
fn apply_sphere_geometry_property(
|
|
value: &mut SolidHistorySphere,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
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)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let center = current.transform_point3(glam::DVec3::ZERO);
|
|
if !center.is_finite() || (target - center[axis]).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - center[axis];
|
|
let updated = glam::DMat4::from_translation(delta) * current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.to_cols_array();
|
|
Some(true)
|
|
}
|
|
|
|
fn apply_cone_position_property(
|
|
value: &mut SolidHistoryCone,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
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)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let origin = current.transform_point3(glam::DVec3::ZERO);
|
|
if !origin.is_finite() || (target - origin[axis]).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - origin[axis];
|
|
let updated = glam::DMat4::from_translation(delta) * current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.to_cols_array();
|
|
Some(true)
|
|
}
|
|
|
|
fn apply_cylinder_position_property(
|
|
value: &mut SolidHistoryCylinder,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
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)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let origin = current.transform_point3(glam::DVec3::ZERO);
|
|
if !origin.is_finite() || (target - origin[axis]).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - origin[axis];
|
|
let updated = glam::DMat4::from_translation(delta) * current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.to_cols_array();
|
|
Some(true)
|
|
}
|
|
|
|
fn apply_sweep_geometry_property(
|
|
value: &mut SolidHistorySweep,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
match field {
|
|
PROP_PROFILE_ROTATION => {
|
|
let target = crate::entities::common::parse_angle(text)?;
|
|
if !target.is_finite() || (target - value.align_angle).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.align_angle = target;
|
|
Some(true)
|
|
}
|
|
PROP_TWIST_ALONG_PATH => {
|
|
let target = crate::entities::common::parse_angle(text)?;
|
|
if !target.is_finite() || (target - value.twist_angle).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.twist_angle = target;
|
|
Some(true)
|
|
}
|
|
PROP_SCALE_ALONG_PATH => {
|
|
let target = text.trim().replace(',', ".").parse::<f64>().ok()?;
|
|
if !target.is_finite()
|
|
|| target <= 1e-9
|
|
|| (target - value.scale_factor).abs() <= 1e-12
|
|
{
|
|
return Some(false);
|
|
}
|
|
value.scale_factor = target;
|
|
Some(true)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn apply_torus_position_property(
|
|
value: &mut SolidHistoryTorus,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
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)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let origin = current.transform_point3(glam::DVec3::ZERO);
|
|
if !origin.is_finite() || (target - origin[axis]).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - origin[axis];
|
|
let updated = glam::DMat4::from_translation(delta) * current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.to_cols_array();
|
|
Some(true)
|
|
}
|
|
|
|
fn canonicalize_cylinder_radii(value: &mut SolidHistoryCylinder) -> bool {
|
|
if value.minor_radius <= value.major_radius {
|
|
value.x_radius = value.major_radius;
|
|
return true;
|
|
}
|
|
let Some(current) = matrix(value.base.transform) else {
|
|
return false;
|
|
};
|
|
std::mem::swap(&mut value.major_radius, &mut value.minor_radius);
|
|
value.x_radius = value.major_radius;
|
|
value.base.transform =
|
|
(current * glam::DMat4::from_rotation_z(std::f64::consts::FRAC_PI_2)).to_cols_array();
|
|
true
|
|
}
|
|
|
|
fn apply_pyramid_geometry_property(
|
|
value: &mut SolidHistoryPyramid,
|
|
field: &str,
|
|
text: &str,
|
|
) -> Option<bool> {
|
|
if field == PROP_PYRAMID_TYPE {
|
|
let next_inscribed = if text.eq_ignore_ascii_case("Inscribed") {
|
|
true
|
|
} else if text.eq_ignore_ascii_case("Circumscribed") {
|
|
false
|
|
} else {
|
|
return Some(false);
|
|
};
|
|
let old_inscribed = pyramid_is_inscribed(value);
|
|
if next_inscribed == old_inscribed {
|
|
return Some(false);
|
|
}
|
|
let factor = pyramid_apothem_factor(value.sides);
|
|
if factor <= 1e-9 {
|
|
return Some(false);
|
|
}
|
|
let base_display = if old_inscribed { value.radius } else { value.radius * factor };
|
|
let top_display = if old_inscribed {
|
|
value.top_radius
|
|
} else {
|
|
value.top_radius * factor
|
|
};
|
|
let Some(current) = matrix(value.base.transform) else {
|
|
return Some(false);
|
|
};
|
|
value.radius = if next_inscribed { base_display } else { base_display / factor };
|
|
value.top_radius = if next_inscribed { top_display } else { top_display / factor };
|
|
let half = std::f64::consts::PI / value.sides.clamp(3, 32) as f64;
|
|
let delta = if next_inscribed { half } else { -half };
|
|
value.base.transform =
|
|
(current * glam::DMat4::from_rotation_z(delta)).to_cols_array();
|
|
value.operation_minor = if next_inscribed { -1 } else { 0 };
|
|
return Some(true);
|
|
}
|
|
if field == PROP_ROTATION {
|
|
let target = crate::entities::common::parse_direction(text)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let projected = current.x_axis.truncate();
|
|
if projected.length_squared() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let current_angle = projected.y.atan2(projected.x);
|
|
let target_vertex_angle = if pyramid_is_inscribed(value) {
|
|
target
|
|
} else {
|
|
target - std::f64::consts::PI / value.sides.clamp(3, 32) as f64
|
|
};
|
|
let delta = (target_vertex_angle - current_angle + std::f64::consts::PI)
|
|
.rem_euclid(std::f64::consts::TAU)
|
|
- std::f64::consts::PI;
|
|
if delta.abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let updated = current * glam::DMat4::from_rotation_z(delta);
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.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)?;
|
|
if !target.is_finite() {
|
|
return Some(false);
|
|
}
|
|
let current = matrix(value.base.transform)?;
|
|
let origin = current.transform_point3(glam::DVec3::ZERO);
|
|
if !origin.is_finite() || (target - origin[axis]).abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
let mut delta = glam::DVec3::ZERO;
|
|
delta[axis] = target - origin[axis];
|
|
let updated = glam::DMat4::from_translation(delta) * current;
|
|
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
|
return Some(false);
|
|
}
|
|
value.base.transform = updated.to_cols_array();
|
|
Some(true)
|
|
}
|
|
|
|
fn set_pyramid_sides(value: &mut SolidHistoryPyramid, sides: i32) -> bool {
|
|
if !(3..=32).contains(&sides) || sides == value.sides {
|
|
return false;
|
|
}
|
|
if !pyramid_is_inscribed(value) {
|
|
let Some(current) = matrix(value.base.transform) else {
|
|
return false;
|
|
};
|
|
let old_factor = pyramid_apothem_factor(value.sides);
|
|
let new_factor = pyramid_apothem_factor(sides);
|
|
value.radius = value.radius * old_factor / new_factor;
|
|
value.top_radius = value.top_radius * old_factor / new_factor;
|
|
let old_half = std::f64::consts::PI / value.sides.clamp(3, 32) as f64;
|
|
let new_half = std::f64::consts::PI / sides as f64;
|
|
value.base.transform =
|
|
(current * glam::DMat4::from_rotation_z(old_half - new_half)).to_cols_array();
|
|
}
|
|
value.sides = sides;
|
|
true
|
|
}
|
|
|
|
pub fn apply_primitive_property(
|
|
operation: &mut SolidHistoryOperation,
|
|
field: &str,
|
|
value: &str,
|
|
) -> bool {
|
|
if let SolidHistoryOperation::Box(rectangular_value)
|
|
| SolidHistoryOperation::Wedge(rectangular_value) = operation
|
|
{
|
|
if let Some(applied) =
|
|
apply_rectangular_geometry_property(rectangular_value, field, value)
|
|
{
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Sphere(sphere_value) = operation {
|
|
if let Some(applied) = apply_sphere_geometry_property(sphere_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Cone(cone_value) = operation {
|
|
if let Some(applied) = apply_cone_position_property(cone_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Cylinder(cylinder_value) = operation {
|
|
if let Some(applied) = apply_cylinder_position_property(cylinder_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Sweep(sweep_value) = operation {
|
|
if let Some(applied) = apply_sweep_geometry_property(sweep_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Extrusion(extrusion_value) = operation {
|
|
if let Some(applied) =
|
|
apply_extrusion_geometry_property(extrusion_value, field, value)
|
|
{
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Revolve(revolve_value) = operation {
|
|
if let Some(applied) = apply_revolve_geometry_property(revolve_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Torus(torus_value) = operation {
|
|
if let Some(applied) = apply_torus_position_property(torus_value, field, value) {
|
|
return applied;
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Pyramid(pyramid_value) = operation {
|
|
if let Some(applied) = apply_pyramid_geometry_property(pyramid_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);
|
|
if let SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) = operation {
|
|
let Some(next) = positive() else {
|
|
return false;
|
|
};
|
|
let (current_size, local_shift) = match field {
|
|
PROP_LENGTH => (
|
|
value.length,
|
|
glam::DVec3::new((value.length - next) * 0.5, 0.0, 0.0),
|
|
),
|
|
PROP_WIDTH => (
|
|
value.width,
|
|
glam::DVec3::new(0.0, (value.width - next) * 0.5, 0.0),
|
|
),
|
|
PROP_HEIGHT => (value.height, glam::DVec3::ZERO),
|
|
_ => return false,
|
|
};
|
|
if !current_size.is_finite() || current_size < 1e-6 || next == current_size {
|
|
return false;
|
|
}
|
|
if local_shift != glam::DVec3::ZERO {
|
|
let Some(current) = matrix(value.base.transform) else {
|
|
return false;
|
|
};
|
|
value.base.transform =
|
|
(current * glam::DMat4::from_translation(local_shift)).to_cols_array();
|
|
}
|
|
match field {
|
|
PROP_LENGTH => value.length = next,
|
|
PROP_WIDTH => value.width = next,
|
|
PROP_HEIGHT => value.height = next,
|
|
_ => unreachable!(),
|
|
}
|
|
return true;
|
|
}
|
|
match operation {
|
|
SolidHistoryOperation::Cylinder(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_MAJOR_RADIUS => {
|
|
value.major_radius = positive().unwrap_or(value.major_radius);
|
|
if !canonicalize_cylinder_radii(value) {
|
|
return false;
|
|
}
|
|
}
|
|
PROP_MINOR_RADIUS => {
|
|
value.minor_radius = positive().unwrap_or(value.minor_radius);
|
|
if !canonicalize_cylinder_radii(value) {
|
|
return false;
|
|
}
|
|
}
|
|
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
|
_ => return false,
|
|
},
|
|
SolidHistoryOperation::Cone(value) => match field {
|
|
PROP_BASE_RADIUS => {
|
|
let Some(radius) = positive() else {
|
|
return false;
|
|
};
|
|
let ratio = if value.base_x_radius > 1e-9 {
|
|
value.base_y_radius / value.base_x_radius
|
|
} else {
|
|
1.0
|
|
};
|
|
value.base_x_radius = radius;
|
|
value.base_y_radius = radius * ratio;
|
|
}
|
|
PROP_BASE_MAJOR_RADIUS => {
|
|
value.base_x_radius = positive().unwrap_or(value.base_x_radius);
|
|
}
|
|
PROP_BASE_MINOR_RADIUS => {
|
|
value.base_y_radius = positive().unwrap_or(value.base_y_radius);
|
|
}
|
|
PROP_TOP_RADIUS => {
|
|
if number < 0.0 {
|
|
return false;
|
|
}
|
|
value.top_radius = number;
|
|
}
|
|
PROP_TOP_MAJOR_RADIUS => {
|
|
if number < 0.0 {
|
|
return false;
|
|
}
|
|
value.top_radius = number;
|
|
}
|
|
PROP_TOP_MINOR_RADIUS => {
|
|
if number < 0.0 || value.base_y_radius.abs() <= 1e-9 {
|
|
return false;
|
|
}
|
|
value.top_radius = number * value.base_x_radius / value.base_y_radius;
|
|
}
|
|
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
|
_ => return false,
|
|
},
|
|
SolidHistoryOperation::Sphere(value) => {
|
|
let Some(next) = positive() else {
|
|
return false;
|
|
};
|
|
let radius = match field {
|
|
PROP_RADIUS => next,
|
|
PROP_DIAMETER => next * 0.5,
|
|
_ => return false,
|
|
};
|
|
if (radius - value.radius).abs() <= 1e-12 {
|
|
return false;
|
|
}
|
|
value.radius = radius;
|
|
}
|
|
SolidHistoryOperation::Torus(value) => match field {
|
|
PROP_MAJOR_RADIUS => {
|
|
let Some(radius) = positive() else {
|
|
return false;
|
|
};
|
|
if (radius - value.major_radius).abs() <= 1e-12 {
|
|
return false;
|
|
}
|
|
value.major_radius = radius;
|
|
}
|
|
PROP_MINOR_RADIUS => {
|
|
let Some(radius) = positive() else {
|
|
return false;
|
|
};
|
|
if (radius - value.minor_radius).abs() <= 1e-12 {
|
|
return false;
|
|
}
|
|
value.minor_radius = radius;
|
|
}
|
|
_ => return false,
|
|
},
|
|
SolidHistoryOperation::Pyramid(value) => match field {
|
|
PROP_BASE_RADIUS => {
|
|
let Some(displayed) = positive() else {
|
|
return false;
|
|
};
|
|
value.radius = if pyramid_is_inscribed(value) {
|
|
displayed
|
|
} else {
|
|
displayed / pyramid_apothem_factor(value.sides)
|
|
};
|
|
}
|
|
PROP_TOP_RADIUS => {
|
|
if number < 0.0 {
|
|
return false;
|
|
}
|
|
value.top_radius = if pyramid_is_inscribed(value) {
|
|
number
|
|
} else {
|
|
number / pyramid_apothem_factor(value.sides)
|
|
};
|
|
}
|
|
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
|
PROP_SIDES => {
|
|
let rounded = number.round();
|
|
if (number - rounded).abs() > 1e-9 {
|
|
return false;
|
|
}
|
|
return set_pyramid_sides(value, rounded as i32);
|
|
}
|
|
_ => return false,
|
|
},
|
|
_ => return false,
|
|
}
|
|
positive().is_some()
|
|
|| field == PROP_SIDES
|
|
|| matches!(
|
|
field,
|
|
PROP_TOP_RADIUS | PROP_TOP_MAJOR_RADIUS | PROP_TOP_MINOR_RADIUS
|
|
)
|
|
}
|
|
|
|
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 embedded_entity(value: &EmbeddedEntity) -> Option<EntityType> {
|
|
Some(match value {
|
|
EmbeddedEntity::Point(value) => EntityType::Point(value.clone()),
|
|
EmbeddedEntity::Line(value) => EntityType::Line(value.clone()),
|
|
EmbeddedEntity::Arc(value) => EntityType::Arc(value.clone()),
|
|
EmbeddedEntity::Circle(value) => EntityType::Circle(value.clone()),
|
|
EmbeddedEntity::Ellipse(value) => EntityType::Ellipse(value.clone()),
|
|
EmbeddedEntity::Spline(value) => EntityType::Spline(value.clone()),
|
|
EmbeddedEntity::LwPolyline(value) => EntityType::LwPolyline(value.clone()),
|
|
EmbeddedEntity::Ray(value) => EntityType::Ray(value.clone()),
|
|
EmbeddedEntity::XLine(value) => EntityType::XLine(value.clone()),
|
|
EmbeddedEntity::Unknown { .. } => return None,
|
|
})
|
|
}
|
|
|
|
fn into_embedded_entity(value: EntityType) -> Option<EmbeddedEntity> {
|
|
Some(match value {
|
|
EntityType::Point(value) => EmbeddedEntity::Point(value),
|
|
EntityType::Line(value) => EmbeddedEntity::Line(value),
|
|
EntityType::Arc(value) => EmbeddedEntity::Arc(value),
|
|
EntityType::Circle(value) => EmbeddedEntity::Circle(value),
|
|
EntityType::Ellipse(value) => EmbeddedEntity::Ellipse(value),
|
|
EntityType::Spline(value) => EmbeddedEntity::Spline(value),
|
|
EntityType::LwPolyline(value) => EmbeddedEntity::LwPolyline(value),
|
|
EntityType::Ray(value) => EmbeddedEntity::Ray(value),
|
|
EntityType::XLine(value) => EmbeddedEntity::XLine(value),
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
fn profile_matrix(value: &SolidHistorySweep) -> Option<glam::DMat4> {
|
|
Some(matrix(value.base.transform)? * matrix(value.sweep_entity_transform)?)
|
|
}
|
|
|
|
fn extrusion_profile_grips(value: &SolidHistorySweep) -> (Vec<GripDef>, Option<glam::DVec3>) {
|
|
let Some(profile) = value.sweep_entity.as_ref().and_then(embedded_entity) else {
|
|
return (Vec::new(), None);
|
|
};
|
|
let Some(transform) = profile_matrix(value) else {
|
|
return (Vec::new(), None);
|
|
};
|
|
let source = profile.grips();
|
|
if source.is_empty() {
|
|
return (Vec::new(), None);
|
|
}
|
|
|
|
let mut center = glam::DVec3::ZERO;
|
|
let mut count = 0.0;
|
|
let grips = source
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(index, grip)| {
|
|
let world = transform.transform_point3(grip.world);
|
|
if !world.is_finite() {
|
|
return None;
|
|
}
|
|
center += world;
|
|
count += 1.0;
|
|
let transform_vector = |vector: glam::DVec3| {
|
|
let vector = transform.transform_vector3(vector);
|
|
(vector.length_squared() > 1e-12).then(|| vector.normalize())
|
|
};
|
|
Some(GripDef {
|
|
id: GRIP_PROFILE_FIRST + index,
|
|
world,
|
|
is_midpoint: grip.is_midpoint,
|
|
shape: grip.shape,
|
|
dir: grip.dir.and_then(transform_vector),
|
|
axis: grip.axis.and_then(transform_vector),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let center = (count > 0.0).then_some(center / count);
|
|
(grips, center)
|
|
}
|
|
|
|
fn apply_extrusion_profile_grip(
|
|
value: &mut SolidHistorySweep,
|
|
index: usize,
|
|
apply: GripApply,
|
|
) -> bool {
|
|
let Some(mut profile) = value.sweep_entity.as_ref().and_then(embedded_entity) else {
|
|
return false;
|
|
};
|
|
let Some(source_grip) = profile.grips().get(index).cloned() else {
|
|
return false;
|
|
};
|
|
let Some(transform) = profile_matrix(value) else {
|
|
return false;
|
|
};
|
|
let inverse = transform.inverse();
|
|
if !inverse.is_finite() {
|
|
return false;
|
|
}
|
|
let local_apply = match apply {
|
|
GripApply::Absolute(world) => {
|
|
let local = inverse.transform_point3(world);
|
|
if !local.is_finite() {
|
|
return false;
|
|
}
|
|
GripApply::Absolute(local)
|
|
}
|
|
GripApply::Translate(world_delta) => {
|
|
let local_delta = inverse.transform_vector3(world_delta);
|
|
if !local_delta.is_finite() || local_delta.length_squared() <= 1e-24 {
|
|
return false;
|
|
}
|
|
GripApply::Translate(local_delta)
|
|
}
|
|
};
|
|
profile.apply_grip(source_grip.id, local_apply);
|
|
let Some(profile) = into_embedded_entity(profile) else {
|
|
return false;
|
|
};
|
|
value.sweep_entity = Some(profile);
|
|
true
|
|
}
|
|
|
|
fn sweep_embedded_grips(
|
|
value: Option<&EmbeddedEntity>,
|
|
base_transform: [f64; 16],
|
|
entity_transform: [f64; 16],
|
|
first_id: usize,
|
|
) -> Vec<GripDef> {
|
|
let Some(entity) = value.and_then(embedded_entity) else {
|
|
return Vec::new();
|
|
};
|
|
let (Some(base), Some(entity_placement)) =
|
|
(matrix(base_transform), matrix(entity_transform))
|
|
else {
|
|
return Vec::new();
|
|
};
|
|
let transform = base * entity_placement;
|
|
entity
|
|
.grips()
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(index, grip)| {
|
|
let world = transform.transform_point3(grip.world);
|
|
if !world.is_finite() {
|
|
return None;
|
|
}
|
|
let transform_vector = |vector: glam::DVec3| {
|
|
let vector = transform.transform_vector3(vector);
|
|
(vector.length_squared() > 1e-12).then(|| vector.normalize())
|
|
};
|
|
Some(GripDef {
|
|
id: first_id + index,
|
|
world,
|
|
is_midpoint: grip.is_midpoint,
|
|
shape: grip.shape,
|
|
dir: grip.dir.and_then(transform_vector),
|
|
axis: grip.axis.and_then(transform_vector),
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn apply_sweep_embedded_grip(
|
|
value: &mut Option<EmbeddedEntity>,
|
|
base_transform: [f64; 16],
|
|
entity_transform: [f64; 16],
|
|
index: usize,
|
|
apply: GripApply,
|
|
) -> bool {
|
|
let Some(mut entity) = value.as_ref().and_then(embedded_entity) else {
|
|
return false;
|
|
};
|
|
let Some(source_grip) = entity.grips().get(index).cloned() else {
|
|
return false;
|
|
};
|
|
let (Some(base), Some(entity_placement)) =
|
|
(matrix(base_transform), matrix(entity_transform))
|
|
else {
|
|
return false;
|
|
};
|
|
let inverse = (base * entity_placement).inverse();
|
|
if !inverse.is_finite() {
|
|
return false;
|
|
}
|
|
let apply = match apply {
|
|
GripApply::Absolute(world) => {
|
|
let local = inverse.transform_point3(world);
|
|
if !local.is_finite() {
|
|
return false;
|
|
}
|
|
GripApply::Absolute(local)
|
|
}
|
|
GripApply::Translate(world_delta) => {
|
|
let local_delta = inverse.transform_vector3(world_delta);
|
|
if !local_delta.is_finite() || local_delta.length_squared() <= 1e-24 {
|
|
return false;
|
|
}
|
|
GripApply::Translate(local_delta)
|
|
}
|
|
};
|
|
entity.apply_grip(source_grip.id, apply);
|
|
let Some(entity) = into_embedded_entity(entity) else {
|
|
return false;
|
|
};
|
|
*value = Some(entity);
|
|
true
|
|
}
|
|
|
|
fn loft_section_grips(value: &SolidHistoryLoft) -> Vec<GripDef> {
|
|
if !value.guides.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
let Some(transform) = matrix(value.base.transform) else {
|
|
return Vec::new();
|
|
};
|
|
let mut result = Vec::new();
|
|
let mut index = 0;
|
|
for section in &value.cross_sections {
|
|
let Some(entity) = embedded_entity(section) else {
|
|
continue;
|
|
};
|
|
for source in entity.grips() {
|
|
let id = GRIP_LOFT_SECTION_FIRST + index;
|
|
index += 1;
|
|
let world = transform.transform_point3(source.world);
|
|
if !world.is_finite() {
|
|
continue;
|
|
}
|
|
let transform_vector = |vector: glam::DVec3| {
|
|
let vector = transform.transform_vector3(vector);
|
|
(vector.length_squared() > 1e-12).then(|| vector.normalize())
|
|
};
|
|
result.push(GripDef {
|
|
id,
|
|
world,
|
|
is_midpoint: source.is_midpoint,
|
|
shape: source.shape,
|
|
dir: source.dir.and_then(transform_vector),
|
|
axis: source.axis.and_then(transform_vector),
|
|
});
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
fn apply_loft_section_grip(
|
|
value: &mut SolidHistoryLoft,
|
|
mut index: usize,
|
|
apply: GripApply,
|
|
) -> bool {
|
|
if !value.guides.is_empty() {
|
|
return false;
|
|
}
|
|
let Some(transform) = matrix(value.base.transform) else {
|
|
return false;
|
|
};
|
|
let inverse = transform.inverse();
|
|
if !inverse.is_finite() {
|
|
return false;
|
|
}
|
|
let local_apply = match apply {
|
|
GripApply::Absolute(world) => {
|
|
let local = inverse.transform_point3(world);
|
|
if !local.is_finite() {
|
|
return false;
|
|
}
|
|
GripApply::Absolute(local)
|
|
}
|
|
GripApply::Translate(world_delta) => {
|
|
let local_delta = inverse.transform_vector3(world_delta);
|
|
if !local_delta.is_finite() || local_delta.length_squared() <= 1e-24 {
|
|
return false;
|
|
}
|
|
GripApply::Translate(local_delta)
|
|
}
|
|
};
|
|
for section in &mut value.cross_sections {
|
|
let Some(mut entity) = embedded_entity(section) else {
|
|
continue;
|
|
};
|
|
let grips = entity.grips();
|
|
if index >= grips.len() {
|
|
index -= grips.len();
|
|
continue;
|
|
}
|
|
let grip_id = grips[index].id;
|
|
entity.apply_grip(grip_id, local_apply);
|
|
let Some(entity) = into_embedded_entity(entity) else {
|
|
return false;
|
|
};
|
|
*section = entity;
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
|
|
fn revolve_profile_matrix(value: &SolidHistoryRevolve) -> Option<glam::DMat4> {
|
|
let base = matrix(value.base.transform)?;
|
|
let axis_point = glam::DVec3::new(
|
|
value.axis_point.x,
|
|
value.axis_point.y,
|
|
value.axis_point.z,
|
|
);
|
|
let axis = glam::DVec3::new(value.direction.x, value.direction.y, value.direction.z)
|
|
.try_normalize()?;
|
|
if !axis_point.is_finite() || !value.start_angle.is_finite() {
|
|
return None;
|
|
}
|
|
Some(
|
|
base
|
|
* glam::DMat4::from_translation(axis_point)
|
|
* glam::DMat4::from_axis_angle(axis, value.start_angle)
|
|
* glam::DMat4::from_translation(-axis_point),
|
|
)
|
|
}
|
|
|
|
fn revolve_profile_grips(value: &SolidHistoryRevolve) -> Vec<GripDef> {
|
|
let Some(profile) = value.sweep_entity.as_ref().and_then(embedded_entity) else {
|
|
return Vec::new();
|
|
};
|
|
let Some(transform) = revolve_profile_matrix(value) else {
|
|
return Vec::new();
|
|
};
|
|
profile
|
|
.grips()
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(index, source)| {
|
|
let world = transform.transform_point3(source.world);
|
|
if !world.is_finite() {
|
|
return None;
|
|
}
|
|
let transform_vector = |vector: glam::DVec3| {
|
|
let vector = transform.transform_vector3(vector);
|
|
(vector.length_squared() > 1e-12).then(|| vector.normalize())
|
|
};
|
|
Some(GripDef {
|
|
id: GRIP_REVOLVE_PROFILE_FIRST + index,
|
|
world,
|
|
is_midpoint: source.is_midpoint,
|
|
shape: source.shape,
|
|
dir: source.dir.and_then(transform_vector),
|
|
axis: source.axis.and_then(transform_vector),
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn apply_revolve_profile_grip(
|
|
value: &mut SolidHistoryRevolve,
|
|
index: usize,
|
|
apply: GripApply,
|
|
) -> bool {
|
|
let Some(mut profile) = value.sweep_entity.as_ref().and_then(embedded_entity) else {
|
|
return false;
|
|
};
|
|
let Some(source) = profile.grips().get(index).cloned() else {
|
|
return false;
|
|
};
|
|
let Some(transform) = revolve_profile_matrix(value) else {
|
|
return false;
|
|
};
|
|
let inverse = transform.inverse();
|
|
if !inverse.is_finite() {
|
|
return false;
|
|
}
|
|
let apply = match apply {
|
|
GripApply::Absolute(world) => {
|
|
let local = inverse.transform_point3(world);
|
|
if !local.is_finite() {
|
|
return false;
|
|
}
|
|
GripApply::Absolute(local)
|
|
}
|
|
GripApply::Translate(delta) => {
|
|
let local = inverse.transform_vector3(delta);
|
|
if !local.is_finite() || local.length_squared() <= 1e-24 {
|
|
return false;
|
|
}
|
|
GripApply::Translate(local)
|
|
}
|
|
};
|
|
profile.apply_grip(source.id, apply);
|
|
let Some(profile) = into_embedded_entity(profile) else {
|
|
return false;
|
|
};
|
|
value.sweep_entity = Some(profile);
|
|
true
|
|
}
|
|
|
|
fn extrusion_draft_grip(
|
|
value: &SolidHistorySweep,
|
|
) -> Option<(glam::DVec3, glam::DVec3, glam::DVec3, f64, f64)> {
|
|
if value.path_entity.is_some() {
|
|
return None;
|
|
}
|
|
let (profile_grips, center) = extrusion_profile_grips(value);
|
|
let center = center?;
|
|
let transform = profile_matrix(value)?;
|
|
let inverse = transform.inverse();
|
|
if !inverse.is_finite() {
|
|
return None;
|
|
}
|
|
let profile = value.sweep_entity.as_ref().and_then(embedded_entity)?;
|
|
let normal = crate::entities::curve::entity_curve(&profile)?.plane.normal()?;
|
|
let normal = transform
|
|
.transform_vector3(glam::DVec3::from_array(normal))
|
|
.try_normalize()?;
|
|
let center_local = inverse.transform_point3(center);
|
|
let anchor = profile_grips
|
|
.into_iter()
|
|
.filter_map(|grip| {
|
|
let local = inverse.transform_point3(grip.world);
|
|
let radius = (grip.world - center).length_squared();
|
|
(local.is_finite() && radius > 1e-12).then_some((grip.world, local))
|
|
})
|
|
.min_by(|(_, a), (_, b)| {
|
|
a.x.total_cmp(&b.x)
|
|
.then_with(|| {
|
|
(a.y - center_local.y)
|
|
.abs()
|
|
.total_cmp(&(b.y - center_local.y).abs())
|
|
})
|
|
.then_with(|| {
|
|
(a.z - center_local.z)
|
|
.abs()
|
|
.total_cmp(&(b.z - center_local.z).abs())
|
|
})
|
|
})?
|
|
.0;
|
|
let radial = anchor - center;
|
|
let radial_length = radial.length();
|
|
if !radial_length.is_finite() || radial_length <= 1e-6 {
|
|
return None;
|
|
}
|
|
let radial = radial / radial_length;
|
|
let base = matrix(value.base.transform)?;
|
|
let direction = base.transform_vector3(glam::DVec3::new(
|
|
value.direction.x,
|
|
value.direction.y,
|
|
value.direction.z,
|
|
));
|
|
let height = direction.dot(normal).abs();
|
|
if !direction.is_finite() || !height.is_finite() || height <= 1e-6 {
|
|
return None;
|
|
}
|
|
let draft_offset = -height * value.draft_angle.tan();
|
|
if !draft_offset.is_finite() {
|
|
return None;
|
|
}
|
|
let base_top = anchor + direction;
|
|
let world = base_top + radial * draft_offset;
|
|
world
|
|
.is_finite()
|
|
.then_some((world, base_top, radial, radial_length, height))
|
|
}
|
|
|
|
fn apply_extrusion_draft_grip(value: &mut SolidHistorySweep, apply: GripApply) -> bool {
|
|
let GripApply::Absolute(world) = apply else {
|
|
return false;
|
|
};
|
|
let Some((_, base_top, radial, _, height)) = extrusion_draft_grip(value) else {
|
|
return false;
|
|
};
|
|
let top_offset = (world - base_top).dot(radial);
|
|
if !top_offset.is_finite() {
|
|
return false;
|
|
}
|
|
let limit = 89.0_f64.to_radians();
|
|
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;
|
|
}
|
|
value.draft_angle = angle;
|
|
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,
|
|
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) => {
|
|
let scale = value
|
|
.major_radius
|
|
.abs()
|
|
.max(value.minor_radius.abs())
|
|
.max(1.0);
|
|
if (value.major_radius - value.minor_radius).abs() > 1e-9 * scale {
|
|
add(
|
|
GRIP_MAJOR_RADIUS,
|
|
value.base.transform,
|
|
[value.major_radius, 0.0, value.height * 0.5],
|
|
GripShape::Square,
|
|
None,
|
|
);
|
|
add(
|
|
GRIP_MINOR_RADIUS,
|
|
value.base.transform,
|
|
[0.0, value.minor_radius, value.height * 0.5],
|
|
GripShape::Square,
|
|
None,
|
|
);
|
|
} else {
|
|
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::Cone(value) => {
|
|
add(
|
|
GRIP_RADIUS,
|
|
value.base.transform,
|
|
[value.base_x_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]),
|
|
);
|
|
}
|
|
SolidHistoryOperation::Sphere(value) => add(
|
|
GRIP_RADIUS,
|
|
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,
|
|
None,
|
|
);
|
|
add(
|
|
GRIP_MINOR_RADIUS,
|
|
value.base.transform,
|
|
[value.major_radius, 0.0, value.minor_radius],
|
|
GripShape::Square,
|
|
Some([0.0, 0.0, 1.0]),
|
|
);
|
|
}
|
|
SolidHistoryOperation::Pyramid(value) => {
|
|
add(
|
|
GRIP_RADIUS,
|
|
value.base.transform,
|
|
[value.radius, 0.0, 0.0],
|
|
GripShape::Square,
|
|
None,
|
|
);
|
|
add(
|
|
GRIP_TOP_RADIUS,
|
|
value.base.transform,
|
|
[value.top_radius, 0.0, value.height],
|
|
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, 32) 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,
|
|
);
|
|
}
|
|
SolidHistoryOperation::Sweep(value) => {
|
|
grips.extend(sweep_embedded_grips(
|
|
value.sweep_entity.as_ref(),
|
|
value.base.transform,
|
|
value.sweep_entity_transform,
|
|
GRIP_SWEEP_PROFILE_FIRST,
|
|
));
|
|
grips.extend(sweep_embedded_grips(
|
|
value.path_entity.as_ref(),
|
|
value.base.transform,
|
|
value.path_entity_transform,
|
|
GRIP_SWEEP_PATH_FIRST,
|
|
));
|
|
}
|
|
SolidHistoryOperation::Loft(value) => grips.extend(loft_section_grips(value)),
|
|
SolidHistoryOperation::Revolve(value) => {
|
|
add(
|
|
GRIP_REVOLVE_AXIS,
|
|
value.base.transform,
|
|
[value.axis_point.x, value.axis_point.y, value.axis_point.z],
|
|
GripShape::Square,
|
|
None,
|
|
);
|
|
grips.extend(revolve_profile_grips(value));
|
|
}
|
|
SolidHistoryOperation::Extrusion(value) => {
|
|
let (profile_grips, profile_center) = extrusion_profile_grips(value);
|
|
grips.extend(profile_grips);
|
|
let direction = [value.direction.x, value.direction.y, value.direction.z];
|
|
if value.path_entity.is_none() {
|
|
if let (Some(center), Some(base)) = (profile_center, matrix(value.base.transform)) {
|
|
let direction = base.transform_vector3(glam::DVec3::from_array(direction));
|
|
if direction.length_squared() > 1e-12 {
|
|
grips.push(grip(
|
|
GRIP_HEIGHT,
|
|
center + direction,
|
|
GripShape::Triangle,
|
|
Some(direction.normalize()),
|
|
));
|
|
}
|
|
}
|
|
if let Some((world, _, radial, _, _)) = extrusion_draft_grip(value) {
|
|
grips.push(grip(
|
|
GRIP_DRAFT,
|
|
world,
|
|
GripShape::Triangle,
|
|
Some(radial),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
grips
|
|
}
|
|
|
|
pub fn apply_primitive_grip(
|
|
operation: &mut SolidHistoryOperation,
|
|
grip_id: usize,
|
|
apply: GripApply,
|
|
) -> bool {
|
|
if let SolidHistoryOperation::Revolve(value) = operation {
|
|
if let Some(index) = grip_id.checked_sub(GRIP_REVOLVE_PROFILE_FIRST) {
|
|
return apply_revolve_profile_grip(value, index, apply);
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Loft(value) = operation {
|
|
if let Some(index) = grip_id.checked_sub(GRIP_LOFT_SECTION_FIRST) {
|
|
return apply_loft_section_grip(value, index, apply);
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Sweep(value) = operation {
|
|
if let Some(index) = grip_id.checked_sub(GRIP_SWEEP_PATH_FIRST) {
|
|
return apply_sweep_embedded_grip(
|
|
&mut value.path_entity,
|
|
value.base.transform,
|
|
value.path_entity_transform,
|
|
index,
|
|
apply,
|
|
);
|
|
}
|
|
if let Some(index) = grip_id
|
|
.checked_sub(GRIP_SWEEP_PROFILE_FIRST)
|
|
.filter(|_| grip_id < GRIP_SWEEP_PATH_FIRST)
|
|
{
|
|
return apply_sweep_embedded_grip(
|
|
&mut value.sweep_entity,
|
|
value.base.transform,
|
|
value.sweep_entity_transform,
|
|
index,
|
|
apply,
|
|
);
|
|
}
|
|
}
|
|
if let SolidHistoryOperation::Extrusion(value) = operation {
|
|
if let Some(index) = grip_id.checked_sub(GRIP_PROFILE_FIRST) {
|
|
return apply_extrusion_profile_grip(value, index, apply);
|
|
}
|
|
if grip_id == GRIP_DRAFT {
|
|
return apply_extrusion_draft_grip(value, apply);
|
|
}
|
|
}
|
|
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;
|
|
};
|
|
if !local.is_finite() {
|
|
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);
|
|
if !old_size.is_finite() || old_size.min_element() < 1e-6 {
|
|
return false;
|
|
}
|
|
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.is_finite() || size.min_element() < 1e-6 {
|
|
return false;
|
|
}
|
|
if low.abs().max_element() <= 1e-12
|
|
&& (size - old_size).abs().max_element() <= 1e-12
|
|
{
|
|
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) => {
|
|
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_MAJOR_RADIUS => {
|
|
value.major_radius = local.x.abs().max(1e-6);
|
|
if !canonicalize_cylinder_radii(value) {
|
|
return false;
|
|
}
|
|
}
|
|
GRIP_MINOR_RADIUS => {
|
|
value.minor_radius = local.y.abs().max(1e-6);
|
|
if !canonicalize_cylinder_radii(value) {
|
|
return false;
|
|
}
|
|
}
|
|
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
|
_ => return false,
|
|
}
|
|
}
|
|
SolidHistoryOperation::Cone(value) => match grip_id {
|
|
GRIP_RADIUS => {
|
|
let radius = local.x.hypot(local.y).max(1e-6);
|
|
let ratio = if value.base_x_radius > 1e-9 {
|
|
value.base_y_radius / value.base_x_radius
|
|
} else {
|
|
1.0
|
|
};
|
|
value.base_x_radius = radius;
|
|
value.base_y_radius = radius * ratio;
|
|
}
|
|
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_MAJOR_RADIUS => value.major_radius = local.x.hypot(local.y).max(1e-6),
|
|
GRIP_MINOR_RADIUS => value.minor_radius = local.z.abs().max(1e-6),
|
|
_ => return false,
|
|
},
|
|
SolidHistoryOperation::Pyramid(value) => match grip_id {
|
|
GRIP_RADIUS => value.radius = local.x.hypot(local.y).max(1e-6),
|
|
GRIP_TOP_RADIUS => {
|
|
value.top_radius = local.x.hypot(local.y).max(0.0);
|
|
}
|
|
GRIP_HEIGHT => value.height = local.z.max(1e-6),
|
|
GRIP_SIDES => {
|
|
let angle = local.y.atan2(local.x).rem_euclid(std::f64::consts::TAU);
|
|
let sides = ((angle.to_degrees() / 5.0).round() as i32).clamp(3, 32);
|
|
if !set_pyramid_sides(value, sides) {
|
|
return false;
|
|
}
|
|
}
|
|
_ => return false,
|
|
},
|
|
SolidHistoryOperation::Revolve(value) if grip_id == GRIP_REVOLVE_AXIS => {
|
|
value.axis_point = acadrust::types::Vector3::new(local.x, local.y, local.z);
|
|
}
|
|
SolidHistoryOperation::Extrusion(value) => match grip_id {
|
|
GRIP_HEIGHT => {
|
|
if value.path_entity.is_some() {
|
|
return false;
|
|
}
|
|
let (_, Some(profile_center)) = extrusion_profile_grips(value) else {
|
|
return false;
|
|
};
|
|
let Some(current) = matrix(value.base.transform) else {
|
|
return false;
|
|
};
|
|
let direction = current
|
|
.inverse()
|
|
.transform_vector3(world - profile_center);
|
|
let height = direction.length();
|
|
if !direction.is_finite() || !height.is_finite() || height <= 1e-6 {
|
|
return false;
|
|
}
|
|
let current = glam::DVec3::new(
|
|
value.direction.x,
|
|
value.direction.y,
|
|
value.direction.z,
|
|
);
|
|
if (direction - current).length_squared() <= 1e-24 {
|
|
return false;
|
|
}
|
|
value.direction = acadrust::types::Vector3::new(
|
|
direction.x,
|
|
direction.y,
|
|
direction.z,
|
|
);
|
|
value.end_draft_distance = height;
|
|
}
|
|
_ => 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 elliptical_cylinder_op(
|
|
transform: [f64; 16],
|
|
major_radius: f64,
|
|
minor_radius: f64,
|
|
height: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Cylinder(SolidHistoryCylinder {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
height,
|
|
major_radius,
|
|
minor_radius,
|
|
x_radius: major_radius,
|
|
..SolidHistoryCylinder::default()
|
|
})
|
|
}
|
|
|
|
pub fn cone_op(
|
|
transform: [f64; 16],
|
|
base_x_radius: f64,
|
|
base_y_radius: f64,
|
|
top_radius: f64,
|
|
height: f64,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Cone(SolidHistoryCone {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
height,
|
|
base_x_radius,
|
|
base_y_radius,
|
|
top_radius,
|
|
..SolidHistoryCone::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,
|
|
top_radius: f64,
|
|
height: f64,
|
|
sides: usize,
|
|
inscribed: bool,
|
|
) -> SolidHistoryOperation {
|
|
SolidHistoryOperation::Pyramid(SolidHistoryPyramid {
|
|
base: base(transform),
|
|
operation_major: 1,
|
|
operation_minor: if inscribed { -1 } else { 0 },
|
|
height,
|
|
sides: sides as i32,
|
|
radius,
|
|
top_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()
|
|
})
|
|
}
|