forked from 1b-agent/opencadstudio-upstream
Merge pull request #950 from ramox81/codex/sphere-command-properties
Complete sphere command, properties, and wireframe behavior
This commit is contained in:
5 files changed
+591
-19
No files matched your search
+18
-5
@@ -441,14 +441,14 @@ impl OpenCADStudio {
|
||||
let display_entity = dispatch::entity_in_working_plane(contextual.as_ref(), plane);
|
||||
let entity = &display_entity;
|
||||
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
||||
let box_primitive =
|
||||
crate::scene::model::solid_history::is_box_primitive(
|
||||
let specialized_primitive =
|
||||
crate::scene::model::solid_history::has_specialized_primitive_properties(
|
||||
&self.tabs[i].scene.document,
|
||||
handle,
|
||||
);
|
||||
let mut sections =
|
||||
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
||||
if box_primitive {
|
||||
if specialized_primitive {
|
||||
sections.retain(|section| {
|
||||
!section.props.iter().any(|property| {
|
||||
property.field.starts_with("acis_")
|
||||
@@ -620,7 +620,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
if !box_primitive && matches!(
|
||||
if !specialized_primitive && matches!(
|
||||
entity,
|
||||
acadrust::EntityType::Solid3D(_)
|
||||
| acadrust::EntityType::Region(_)
|
||||
@@ -694,7 +694,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
if !box_primitive {
|
||||
if !specialized_primitive {
|
||||
sections.extend(crate::entities::object_data::sections(
|
||||
&self.tabs[i].scene.document,
|
||||
&self.tabs[i].scene.object_data_cache,
|
||||
@@ -2146,6 +2146,19 @@ impl OpenCADStudio {
|
||||
.map(|(handle, entity)| (*handle, entity))
|
||||
.collect();
|
||||
let mut sections = aggregate_sections(&local_refs, &text_style_names);
|
||||
if local_refs.iter().all(|(handle, _)| {
|
||||
crate::scene::model::solid_history::has_specialized_primitive_properties(
|
||||
&self.tabs[i].scene.document,
|
||||
*handle,
|
||||
)
|
||||
}) {
|
||||
sections.retain(|section| {
|
||||
!section.props.iter().any(|property| {
|
||||
property.field.starts_with("acis_")
|
||||
|| property.field.starts_with("s3d_")
|
||||
})
|
||||
});
|
||||
}
|
||||
sections.extend(aggregate_solid_history_sections(
|
||||
&self.tabs[i].scene.document,
|
||||
&local_refs.iter().map(|(handle, _)| *handle).collect::<Vec<_>>(),
|
||||
|
||||
@@ -4,9 +4,14 @@ use acadrust::entities::Solid3D;
|
||||
use acadrust::objects::SolidHistoryOperation;
|
||||
use acadrust::EntityType;
|
||||
use cadkernel::brep::Body;
|
||||
use cadkernel::geom2d::{
|
||||
fillets_between, Circle as KernelCircle, Curve as KernelCurve, Line as KernelLine,
|
||||
Tolerance,
|
||||
};
|
||||
use glam::DVec3;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult, WorkingPlane};
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult, TangentObject, WorkingPlane};
|
||||
use crate::scene::model::solid_model;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use crate::t;
|
||||
@@ -36,6 +41,116 @@ enum BoxStep {
|
||||
HeightSecondPoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SphereStep {
|
||||
Center,
|
||||
Radius(DVec3),
|
||||
Diameter(DVec3),
|
||||
TwoPointFirst,
|
||||
TwoPointSecond(DVec3),
|
||||
ThreePointFirst,
|
||||
ThreePointSecond(DVec3),
|
||||
ThreePointThird(DVec3, DVec3),
|
||||
TtrFirst,
|
||||
TtrSecond {
|
||||
object: TangentObject,
|
||||
hit: DVec3,
|
||||
},
|
||||
TtrRadius {
|
||||
first: TangentObject,
|
||||
second: TangentObject,
|
||||
first_hit: DVec3,
|
||||
second_hit: DVec3,
|
||||
},
|
||||
}
|
||||
|
||||
fn sphere_radius_store() -> &'static Mutex<f64> {
|
||||
static VALUE: OnceLock<Mutex<f64>> = OnceLock::new();
|
||||
VALUE.get_or_init(|| Mutex::new(1.0))
|
||||
}
|
||||
|
||||
fn sphere_radius_default() -> f64 {
|
||||
sphere_radius_store()
|
||||
.lock()
|
||||
.map(|value| *value)
|
||||
.unwrap_or(1.0)
|
||||
}
|
||||
|
||||
fn remember_sphere_radius(radius: f64) {
|
||||
if radius.is_finite() && radius > 1e-6 {
|
||||
if let Ok(mut value) = sphere_radius_store().lock() {
|
||||
*value = radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sphere_tangent_local(object: TangentObject, plane: WorkingPlane) -> TangentObject {
|
||||
match object {
|
||||
TangentObject::Line { p1, p2 } => TangentObject::Line {
|
||||
p1: plane.to_local(p1),
|
||||
p2: plane.to_local(p2),
|
||||
},
|
||||
TangentObject::Circle { center, radius } => TangentObject::Circle {
|
||||
center: plane.to_local(center),
|
||||
radius,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn sphere_tangent_curve(object: TangentObject) -> KernelCurve {
|
||||
match object {
|
||||
TangentObject::Line { p1, p2 } => KernelCurve::Line(KernelLine {
|
||||
start: [p1.x, p1.y],
|
||||
end: [p2.x, p2.y],
|
||||
}),
|
||||
TangentObject::Circle { center, radius } => KernelCurve::Circle(KernelCircle {
|
||||
centre: [center.x, center.y],
|
||||
radius,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn sphere_ttr_centers(
|
||||
first: TangentObject,
|
||||
second: TangentObject,
|
||||
radius: f64,
|
||||
) -> Vec<DVec3> {
|
||||
fillets_between(
|
||||
&sphere_tangent_curve(first),
|
||||
&sphere_tangent_curve(second),
|
||||
radius,
|
||||
Tolerance::default(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|fillet| DVec3::new(fillet.centre[0], fillet.centre[1], 0.0))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn closest_sphere_center(candidates: &[DVec3], hint: DVec3) -> Option<DVec3> {
|
||||
candidates.iter().copied().min_by(|first, second| {
|
||||
first
|
||||
.distance(hint)
|
||||
.partial_cmp(&second.distance(hint))
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
}
|
||||
|
||||
fn sphere_through_three_points(
|
||||
first: DVec3,
|
||||
second: DVec3,
|
||||
third: DVec3,
|
||||
) -> Option<(DVec3, f64)> {
|
||||
let circle = cadkernel::geom2d::arc_through_points(
|
||||
[first.x, first.y],
|
||||
[second.x, second.y],
|
||||
[third.x, third.y],
|
||||
)?;
|
||||
Some((
|
||||
DVec3::new(circle.centre[0], circle.centre[1], first.z),
|
||||
circle.radius,
|
||||
))
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
fn from_id(id: &str) -> Option<Shape> {
|
||||
Some(match id {
|
||||
@@ -85,6 +200,8 @@ pub struct PrimitiveCommand {
|
||||
box_angle: f64,
|
||||
box_width_sign: f64,
|
||||
box_height_anchor: Option<DVec3>,
|
||||
sphere_step: SphereStep,
|
||||
sphere_default_radius: f64,
|
||||
plane: WorkingPlane,
|
||||
}
|
||||
|
||||
@@ -102,10 +219,52 @@ impl PrimitiveCommand {
|
||||
box_angle: 0.0,
|
||||
box_width_sign: 1.0,
|
||||
box_height_anchor: None,
|
||||
sphere_step: SphereStep::Center,
|
||||
sphere_default_radius: sphere_radius_default(),
|
||||
plane: WorkingPlane::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn commit_sphere(&mut self, center: DVec3, radius: f64) -> CmdResult {
|
||||
if !center.is_finite() || !radius.is_finite() || radius <= 1e-6 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
self.pts = vec![center, center + DVec3::X * radius];
|
||||
self.sphere_default_radius = radius;
|
||||
remember_sphere_radius(radius);
|
||||
self.commit(0.0)
|
||||
}
|
||||
|
||||
fn sphere_ttr_result(&mut self, radius: f64) -> CmdResult {
|
||||
let SphereStep::TtrRadius {
|
||||
first,
|
||||
second,
|
||||
first_hit,
|
||||
second_hit,
|
||||
} = self.sphere_step
|
||||
else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let hint = (first_hit + second_hit) * 0.5;
|
||||
let Some(center) = closest_sphere_center(
|
||||
&sphere_ttr_centers(first, second, radius),
|
||||
hint,
|
||||
) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
self.commit_sphere(center, radius)
|
||||
}
|
||||
|
||||
fn sphere_preview(&self, center: DVec3, radius: f64) -> Option<WireModel> {
|
||||
if !center.is_finite() || !radius.is_finite() || radius <= 1e-6 {
|
||||
return None;
|
||||
}
|
||||
Some(self.place_preview(footprint_wire(
|
||||
Shape::Sphere,
|
||||
&[center, center + DVec3::X * radius],
|
||||
)))
|
||||
}
|
||||
|
||||
/// Number of footprint points the shape needs before the height step.
|
||||
fn footprint_pts(&self) -> usize {
|
||||
match self.shape {
|
||||
@@ -499,6 +658,41 @@ impl CadCommand for PrimitiveCommand {
|
||||
self.shape.name()
|
||||
}
|
||||
|
||||
fn needs_tangent_pick(&self) -> bool {
|
||||
self.shape == Shape::Sphere
|
||||
&& matches!(
|
||||
self.sphere_step,
|
||||
SphereStep::TtrFirst | SphereStep::TtrSecond { .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn on_tangent_point(&mut self, object: TangentObject, hit: DVec3) -> CmdResult {
|
||||
if self.shape != Shape::Sphere {
|
||||
return self.on_point(hit);
|
||||
}
|
||||
let object = sphere_tangent_local(object, self.plane);
|
||||
let hit = self.plane.to_local(hit);
|
||||
match self.sphere_step {
|
||||
SphereStep::TtrFirst => {
|
||||
self.sphere_step = SphereStep::TtrSecond { object, hit };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
SphereStep::TtrSecond {
|
||||
object: first,
|
||||
hit: first_hit,
|
||||
} => {
|
||||
self.sphere_step = SphereStep::TtrRadius {
|
||||
first,
|
||||
second: object,
|
||||
first_hit,
|
||||
second_hit: hit,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
_ => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
let n = self.shape.name();
|
||||
if self.shape == Shape::Box {
|
||||
@@ -521,6 +715,49 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
.into_owned();
|
||||
}
|
||||
if self.shape == Shape::Sphere {
|
||||
return match self.sphere_step {
|
||||
SphereStep::Center => {
|
||||
t!("SPHERE Specify center point or [3P/2P/Ttr]:").into_owned()
|
||||
}
|
||||
SphereStep::Radius(_) => crate::tf!(
|
||||
"SPHERE Specify radius or [Diameter] <{:.4}>:",
|
||||
self.sphere_default_radius
|
||||
)
|
||||
.into_owned(),
|
||||
SphereStep::Diameter(_) => crate::tf!(
|
||||
"SPHERE Specify diameter <{:.4}>:",
|
||||
self.sphere_default_radius * 2.0
|
||||
)
|
||||
.into_owned(),
|
||||
SphereStep::TwoPointFirst => {
|
||||
t!("SPHERE Specify first end point of diameter:").into_owned()
|
||||
}
|
||||
SphereStep::TwoPointSecond(_) => {
|
||||
t!("SPHERE Specify second end point of diameter:").into_owned()
|
||||
}
|
||||
SphereStep::ThreePointFirst => {
|
||||
t!("SPHERE Specify first point on sphere:").into_owned()
|
||||
}
|
||||
SphereStep::ThreePointSecond(_) => {
|
||||
t!("SPHERE Specify second point on sphere:").into_owned()
|
||||
}
|
||||
SphereStep::ThreePointThird(_, _) => {
|
||||
t!("SPHERE Specify third point on sphere:").into_owned()
|
||||
}
|
||||
SphereStep::TtrFirst => {
|
||||
t!("SPHERE Select first tangent object:").into_owned()
|
||||
}
|
||||
SphereStep::TtrSecond { .. } => {
|
||||
t!("SPHERE Select second tangent object:").into_owned()
|
||||
}
|
||||
SphereStep::TtrRadius { .. } => crate::tf!(
|
||||
"SPHERE Specify radius <{:.4}>:",
|
||||
self.sphere_default_radius
|
||||
)
|
||||
.into_owned(),
|
||||
};
|
||||
}
|
||||
if self.height_step {
|
||||
return t!("%{n} Specify height <Enter for default>:", n = n).into_owned();
|
||||
}
|
||||
@@ -540,6 +777,17 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
if self.shape == Shape::Sphere {
|
||||
return match self.sphere_step {
|
||||
SphereStep::Center => vec![
|
||||
CmdOption::new("3P", "3P"),
|
||||
CmdOption::new("2P", "2P"),
|
||||
CmdOption::new("Ttr", "T"),
|
||||
],
|
||||
SphereStep::Radius(_) => vec![CmdOption::new(t!("Diameter").as_ref(), "D")],
|
||||
_ => Vec::new(),
|
||||
};
|
||||
}
|
||||
if self.shape != Shape::Box {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -555,6 +803,49 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if self.shape == Shape::Sphere {
|
||||
let local = self.plane.to_local(pt);
|
||||
if !local.is_finite() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
return match self.sphere_step {
|
||||
SphereStep::Center => {
|
||||
self.sphere_step = SphereStep::Radius(local);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
SphereStep::Radius(center) => self.commit_sphere(center, center.distance(local)),
|
||||
SphereStep::Diameter(center) => {
|
||||
self.commit_sphere(center, center.distance(local) * 0.5)
|
||||
}
|
||||
SphereStep::TwoPointFirst => {
|
||||
self.sphere_step = SphereStep::TwoPointSecond(local);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
SphereStep::TwoPointSecond(first) => {
|
||||
self.commit_sphere((first + local) * 0.5, first.distance(local) * 0.5)
|
||||
}
|
||||
SphereStep::ThreePointFirst => {
|
||||
self.sphere_step = SphereStep::ThreePointSecond(local);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
SphereStep::ThreePointSecond(first) => {
|
||||
self.sphere_step = SphereStep::ThreePointThird(first, local);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
SphereStep::ThreePointThird(first, second) => {
|
||||
let Some((center, radius)) =
|
||||
sphere_through_three_points(first, second, local)
|
||||
else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
self.commit_sphere(center, radius)
|
||||
}
|
||||
SphereStep::TtrRadius { second_hit, .. } => {
|
||||
self.sphere_ttr_result(second_hit.distance(local))
|
||||
}
|
||||
SphereStep::TtrFirst | SphereStep::TtrSecond { .. } => CmdResult::NeedPoint,
|
||||
};
|
||||
}
|
||||
if self.shape == Shape::Box {
|
||||
let local = self.plane.to_local(pt);
|
||||
if !local.is_finite() {
|
||||
@@ -632,6 +923,20 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.shape == Shape::Sphere {
|
||||
return match self.sphere_step {
|
||||
SphereStep::Radius(center) => {
|
||||
self.commit_sphere(center, self.sphere_default_radius)
|
||||
}
|
||||
SphereStep::Diameter(center) => {
|
||||
self.commit_sphere(center, self.sphere_default_radius)
|
||||
}
|
||||
SphereStep::TtrRadius { .. } => {
|
||||
self.sphere_ttr_result(self.sphere_default_radius)
|
||||
}
|
||||
_ => CmdResult::Cancel,
|
||||
};
|
||||
}
|
||||
if self.shape == Shape::Box {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
@@ -647,6 +952,15 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
if self.shape == Shape::Sphere {
|
||||
return matches!(
|
||||
self.sphere_step,
|
||||
SphereStep::Center
|
||||
| SphereStep::Radius(_)
|
||||
| SphereStep::Diameter(_)
|
||||
| SphereStep::TtrRadius { .. }
|
||||
);
|
||||
}
|
||||
if self.shape == Shape::Box {
|
||||
matches!(
|
||||
self.box_step,
|
||||
@@ -663,6 +977,9 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
if self.shape == Shape::Sphere {
|
||||
return matches!(self.sphere_step, SphereStep::Center | SphereStep::Radius(_));
|
||||
}
|
||||
self.shape == Shape::Box
|
||||
&& matches!(
|
||||
self.box_step,
|
||||
@@ -671,6 +988,44 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, raw: &str) -> Option<CmdResult> {
|
||||
if self.shape == Shape::Sphere {
|
||||
let token = raw.trim();
|
||||
let upper = token.to_uppercase();
|
||||
return match self.sphere_step {
|
||||
SphereStep::Center if upper == "3P" => {
|
||||
self.sphere_step = SphereStep::ThreePointFirst;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
SphereStep::Center if upper == "2P" => {
|
||||
self.sphere_step = SphereStep::TwoPointFirst;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
SphereStep::Center if matches!(upper.as_str(), "T" | "TTR") => {
|
||||
self.sphere_step = SphereStep::TtrFirst;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
SphereStep::Radius(_) if matches!(upper.as_str(), "D" | "DIAMETER") => {
|
||||
let SphereStep::Radius(center) = self.sphere_step else {
|
||||
unreachable!();
|
||||
};
|
||||
self.sphere_step = SphereStep::Diameter(center);
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
SphereStep::Radius(center) => {
|
||||
let radius = crate::entities::common::parse_typed_length(token)?;
|
||||
Some(self.commit_sphere(center, radius))
|
||||
}
|
||||
SphereStep::Diameter(center) => {
|
||||
let diameter = crate::entities::common::parse_typed_length(token)?;
|
||||
Some(self.commit_sphere(center, diameter * 0.5))
|
||||
}
|
||||
SphereStep::TtrRadius { .. } => {
|
||||
let radius = crate::entities::common::parse_typed_length(token)?;
|
||||
Some(self.sphere_ttr_result(radius))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
if self.shape == Shape::Box {
|
||||
let token = raw.trim();
|
||||
let upper = token.to_uppercase();
|
||||
@@ -719,6 +1074,43 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
if self.shape == Shape::Sphere {
|
||||
let local = self.plane.to_local(pt);
|
||||
if !local.is_finite() {
|
||||
return None;
|
||||
}
|
||||
return match self.sphere_step {
|
||||
SphereStep::Radius(center) => self.sphere_preview(center, center.distance(local)),
|
||||
SphereStep::Diameter(center) => {
|
||||
self.sphere_preview(center, center.distance(local) * 0.5)
|
||||
}
|
||||
SphereStep::TwoPointSecond(first) => {
|
||||
self.sphere_preview((first + local) * 0.5, first.distance(local) * 0.5)
|
||||
}
|
||||
SphereStep::ThreePointSecond(first) => {
|
||||
self.sphere_preview((first + local) * 0.5, first.distance(local) * 0.5)
|
||||
}
|
||||
SphereStep::ThreePointThird(first, second) => {
|
||||
let (center, radius) = sphere_through_three_points(first, second, local)?;
|
||||
self.sphere_preview(center, radius)
|
||||
}
|
||||
SphereStep::TtrRadius {
|
||||
first,
|
||||
second,
|
||||
first_hit,
|
||||
second_hit,
|
||||
} => {
|
||||
let radius = second_hit.distance(local);
|
||||
let hint = (first_hit + second_hit) * 0.5;
|
||||
let center = closest_sphere_center(
|
||||
&sphere_ttr_centers(first, second, radius),
|
||||
hint,
|
||||
)?;
|
||||
self.sphere_preview(center, radius)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
if self.pts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -806,6 +1198,23 @@ impl CadCommand for PrimitiveCommand {
|
||||
fn dyn_spec(&self) -> Option<crate::command::DynSpec> {
|
||||
use crate::command::{DynAnchor, DynFieldSpec, DynGuide, DynRole, DynSpec};
|
||||
|
||||
if self.shape == Shape::Sphere {
|
||||
let (anchor, role) = match self.sphere_step {
|
||||
SphereStep::Radius(center) => (center, DynRole::Radius),
|
||||
SphereStep::Diameter(center) => (center, DynRole::Diameter),
|
||||
SphereStep::TtrRadius { second_hit, .. } => {
|
||||
(second_hit, DynRole::Radius)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
return Some(DynSpec {
|
||||
anchor: DynAnchor::Point(self.plane.to_world(anchor)),
|
||||
fields: vec![DynFieldSpec::new(role)],
|
||||
guide: DynGuide::Radius,
|
||||
ref_point: None,
|
||||
});
|
||||
}
|
||||
|
||||
let role = if self.shape == Shape::Box {
|
||||
match self.box_step {
|
||||
BoxStep::CubeSize | BoxStep::Length => DynRole::Distance,
|
||||
@@ -827,6 +1236,17 @@ impl CadCommand for PrimitiveCommand {
|
||||
}
|
||||
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
if self.shape == Shape::Sphere {
|
||||
let local = self.plane.to_local(cursor);
|
||||
return match self.sphere_step {
|
||||
SphereStep::Radius(center) => Some(center.distance(local)),
|
||||
SphereStep::Diameter(center) => Some(center.distance(local)),
|
||||
SphereStep::TtrRadius { second_hit, .. } => {
|
||||
Some(second_hit.distance(local))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
if self.shape == Shape::Box {
|
||||
let local = self.plane.to_local(cursor);
|
||||
if !local.is_finite() {
|
||||
|
||||
@@ -31,6 +31,7 @@ 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_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";
|
||||
@@ -73,16 +74,94 @@ pub fn is_box_primitive(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_specialized_primitive_properties(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
) -> bool {
|
||||
matches!(
|
||||
document.solid_history_operation(handle),
|
||||
Some(SolidHistoryOperation::Box(_) | SolidHistoryOperation::Sphere(_))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3> {
|
||||
match operation {
|
||||
SolidHistoryOperation::Box(value) => world_point(
|
||||
value.base.transform,
|
||||
[value.length * 0.5, value.width * 0.5, 0.0],
|
||||
),
|
||||
SolidHistoryOperation::Sphere(value) => {
|
||||
world_point(value.base.transform, [0.0, 0.0, 0.0])
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sphere_properties(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
value: &SolidHistorySphere,
|
||||
) -> Vec<PropSection> {
|
||||
let Some(position) = world_point(value.base.transform, [0.0, 0.0, 0.0]) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let (record_history, show_history) = history_flags(document, handle).unwrap_or((false, false));
|
||||
let history_value = if record_history { "Record" } else { "None" };
|
||||
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::ReadOnly(history_value.to_string()),
|
||||
},
|
||||
Property {
|
||||
label: t!("Show History").into_owned(),
|
||||
field: PROP_SHOW_HISTORY,
|
||||
value: PropValue::ReadOnly(show_value.to_string()),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn box_properties(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
@@ -184,6 +263,9 @@ pub fn primitive_properties(
|
||||
};
|
||||
let props = match operation {
|
||||
SolidHistoryOperation::Box(value) => return box_properties(document, handle, value),
|
||||
SolidHistoryOperation::Sphere(value) => {
|
||||
return sphere_properties(document, handle, value)
|
||||
}
|
||||
SolidHistoryOperation::Wedge(value) => vec![
|
||||
history_prop(t!("Length").as_ref(), PROP_LENGTH, value.length),
|
||||
history_prop(t!("Width").as_ref(), PROP_WIDTH, value.width),
|
||||
@@ -193,11 +275,6 @@ pub fn primitive_properties(
|
||||
history_prop(t!("Radius").as_ref(), PROP_RADIUS, value.major_radius),
|
||||
history_prop(t!("Height").as_ref(), PROP_HEIGHT, value.height),
|
||||
],
|
||||
SolidHistoryOperation::Sphere(value) => vec![history_prop(
|
||||
t!("Radius").as_ref(),
|
||||
PROP_RADIUS,
|
||||
value.radius,
|
||||
)],
|
||||
SolidHistoryOperation::Torus(value) => vec![
|
||||
history_prop(
|
||||
t!("Outer Radius").as_ref(),
|
||||
@@ -230,6 +307,7 @@ pub fn is_primitive_property(field: &str) -> bool {
|
||||
| PROP_WIDTH
|
||||
| PROP_HEIGHT
|
||||
| PROP_RADIUS
|
||||
| PROP_DIAMETER
|
||||
| PROP_OUTER_RADIUS
|
||||
| PROP_INNER_RADIUS
|
||||
| PROP_SIDES
|
||||
@@ -355,6 +433,36 @@ fn apply_box_geometry_property(
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn apply_primitive_property(
|
||||
operation: &mut SolidHistoryOperation,
|
||||
field: &str,
|
||||
@@ -365,6 +473,11 @@ pub fn apply_primitive_property(
|
||||
return applied;
|
||||
}
|
||||
}
|
||||
if let SolidHistoryOperation::Sphere(sphere_value) = operation {
|
||||
if let Some(applied) = apply_sphere_geometry_property(sphere_value, field, value) {
|
||||
return applied;
|
||||
}
|
||||
}
|
||||
let Some(number) = (if field == PROP_SIDES {
|
||||
value.trim().parse::<f64>().ok()
|
||||
} else {
|
||||
@@ -429,8 +542,19 @@ pub fn apply_primitive_property(
|
||||
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
||||
_ => return false,
|
||||
},
|
||||
SolidHistoryOperation::Sphere(value) if field == PROP_RADIUS => {
|
||||
value.radius = positive().unwrap_or(value.radius);
|
||||
SolidHistoryOperation::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) => {
|
||||
let outer = value.major_radius + value.minor_radius;
|
||||
|
||||
@@ -343,8 +343,9 @@ pub struct MeshBatchChunk {
|
||||
/// erase — the geometry behind them.
|
||||
pub transp_index_buffer: wgpu::Buffer,
|
||||
pub transp_index_count: u32,
|
||||
/// Triangle-edge line list (into `vertex_buffer`) for plain meshes that
|
||||
/// carry no B-rep edges — the tessellation wireframe.
|
||||
/// Triangle-edge line list for plain meshes that carry no B-rep edges.
|
||||
/// The dedicated vertex buffer matches the edge pipeline layout.
|
||||
pub wire_vertex_buffer: wgpu::Buffer,
|
||||
pub wire_index_buffer: wgpu::Buffer,
|
||||
pub wire_index_count: u32,
|
||||
/// B-rep feature edges of ACIS solids, as a standalone LineList vertex
|
||||
@@ -478,6 +479,18 @@ fn make_chunk(
|
||||
} else {
|
||||
instances
|
||||
};
|
||||
let wire_vertices: Vec<MeshEdgeVertex> = if wire_indices.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
verts
|
||||
.iter()
|
||||
.map(|vertex| MeshEdgeVertex {
|
||||
position: vertex.position,
|
||||
color: face_color,
|
||||
position_low: vertex.position_low,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
MeshBatchChunk {
|
||||
vertex_buffer: vertex_buffer_override.unwrap_or_else(|| {
|
||||
if compact_vertices {
|
||||
@@ -491,6 +504,7 @@ fn make_chunk(
|
||||
index_count: indices.len() as u32,
|
||||
transp_index_buffer: mk_index(transp_indices, "mesh.batch.transp_ibuf"),
|
||||
transp_index_count: transp_indices.len() as u32,
|
||||
wire_vertex_buffer: mk_edge_vertex(&wire_vertices, "mesh.batch.wire_vbuf"),
|
||||
wire_index_buffer: mk_index(wire_indices, "mesh.batch.wire_ibuf"),
|
||||
wire_index_count: wire_indices.len() as u32,
|
||||
edge_vertex_buffer: edge_buffer_override
|
||||
@@ -1204,7 +1218,8 @@ fn build_instanced_chunk(
|
||||
uv_normal: uvs[6],
|
||||
}
|
||||
};
|
||||
let verts: Vec<_> = if shared_vertex_buffer.is_none() {
|
||||
let needs_wire_vertices = first.include_edges && source.edge_verts.is_empty();
|
||||
let verts: Vec<_> = if shared_vertex_buffer.is_none() || needs_wire_vertices {
|
||||
(0..mesh.verts.len()).map(vertex).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
|
||||
@@ -3810,7 +3810,7 @@ impl Pipeline {
|
||||
pass.set_vertex_buffer(1, c.instance_buffer.slice(..));
|
||||
// Plain-mesh triangulation edges.
|
||||
if c.wire_index_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_vertex_buffer(0, c.wire_vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
@@ -3847,7 +3847,7 @@ impl Pipeline {
|
||||
);
|
||||
pass.set_vertex_buffer(1, c.instance_buffer.slice(..));
|
||||
if c.wire_index_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_vertex_buffer(0, c.wire_vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
@@ -4003,7 +4003,7 @@ impl Pipeline {
|
||||
);
|
||||
pass.set_vertex_buffer(1, c.instance_buffer.slice(..));
|
||||
if c.wire_index_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_vertex_buffer(0, c.wire_vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
|
||||
Reference in new issue
Block a user