forked from 1b-agent/opencadstudio-upstream
feat(block-editor): wire Rotation, Alignment and Flip parameter placement
Enable the existing palette rows and BPARAMETER choices. Rotation accepts a center, picked or numeric radius, and picked or numeric default angle. Alignment places a tangent direction from its base point. Flip places a reflection line and state label, with native grip and state expressions. Extend shared graph append/capacity checks for Flip's additional state node while preserving existing subgraphs. Keep Polar/Rotation angle labels unique in either placement order. Add editor graphics, extents and definition reframing through the existing overlay/history/save paths. This milestone implements native parameter placement and persistence. Rotate/Flip action association, reference alignment/grip editing, parameter property editing and parameter sets remain separate work. Validation covers command and palette placement, cancellation, local/parent undo, save/discard, native DWG/DXF objects, existing graph preservation and the 17 research fixtures. Broader block/dock regressions and editor renders are also checked.
This commit is contained in:
1 parent
6dab5a7229
commit
72c762e944
19 files changed
+1483
-25
No files matched your search
@@ -8,6 +8,7 @@ use crate::modules::{CadModule, IconKind, ModuleEvent, RibbonGroup, RibbonItem,
|
||||
pub mod visibility_parameter;
|
||||
pub mod point_parameter;
|
||||
pub mod distance_parameter;
|
||||
pub mod orientation_parameter;
|
||||
|
||||
pub struct BlockEditorModule;
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
//! Interactive native Rotation, Alignment and Flip placement.
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::{
|
||||
block_orientation_parameter::{self, Kind},
|
||||
WireModel,
|
||||
};
|
||||
use codec::{types::Vector3, Handle};
|
||||
use glam::DVec3;
|
||||
|
||||
pub struct OrientationParameterCommand {
|
||||
document_id: u64,
|
||||
definition: Handle,
|
||||
kind: Kind,
|
||||
base: Option<DVec3>,
|
||||
second: Option<DVec3>,
|
||||
}
|
||||
|
||||
impl OrientationParameterCommand {
|
||||
pub(crate) fn new(document_id: u64, definition: Handle, kind: Kind) -> Self {
|
||||
Self {
|
||||
document_id,
|
||||
definition,
|
||||
kind,
|
||||
base: None,
|
||||
second: None,
|
||||
}
|
||||
}
|
||||
fn finish(&self, end: DVec3, reference: DVec3) -> CmdResult {
|
||||
let base = self.base.unwrap();
|
||||
let vector = |p: DVec3| Vector3::new(p.x, p.y, p.z);
|
||||
if let Some(error) = block_orientation_parameter::points_error(
|
||||
self.kind,
|
||||
vector(base),
|
||||
vector(end),
|
||||
vector(reference),
|
||||
) {
|
||||
return CmdResult::ReportError(crate::t!(error).into_owned());
|
||||
}
|
||||
CmdResult::PlaceBlockOrientationParameter {
|
||||
document_id: self.document_id,
|
||||
definition: self.definition,
|
||||
kind: self.kind,
|
||||
base,
|
||||
end,
|
||||
reference,
|
||||
}
|
||||
}
|
||||
fn rotation_at(&self, angle: f64) -> CmdResult {
|
||||
let base = self.base.unwrap();
|
||||
let reference = self.second.unwrap();
|
||||
let radius = (reference - base).length();
|
||||
self.finish(
|
||||
base + DVec3::new(angle.cos(), angle.sin(), 0.0) * radius,
|
||||
reference,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for OrientationParameterCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"BPARAMETER"
|
||||
}
|
||||
fn prompt(&self) -> String {
|
||||
let step = if self.base.is_none() {
|
||||
"Specify parameter base point:"
|
||||
} else {
|
||||
match (self.kind, self.second.is_some()) {
|
||||
(Kind::Rotation, false) => "Specify parameter radius or pick a radius point:",
|
||||
(Kind::Rotation, true) => "Specify default rotation angle or pick direction <0>:",
|
||||
(Kind::Alignment, _) => "Specify alignment direction (tangent):",
|
||||
(Kind::Flip, false) => "Specify reflection line endpoint:",
|
||||
(Kind::Flip, true) => "Specify flip parameter label location:",
|
||||
}
|
||||
};
|
||||
crate::tf!(
|
||||
"BPARAMETER {} {}",
|
||||
crate::t!(self.kind.name()),
|
||||
crate::t!(step)
|
||||
)
|
||||
.into_owned()
|
||||
}
|
||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||
if !point.is_finite() {
|
||||
return CmdResult::ReportError(
|
||||
crate::t!("Specify a finite parameter location.").into_owned(),
|
||||
);
|
||||
}
|
||||
let Some(base) = self.base else {
|
||||
self.base = Some(point);
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
if (point.z - base.z).abs() > 1e-8 {
|
||||
return CmdResult::ReportError(
|
||||
crate::t!("Specify points in the same block XY plane.").into_owned(),
|
||||
);
|
||||
}
|
||||
if self.kind == Kind::Flip && self.second.is_some() {
|
||||
return self.finish(self.second.unwrap(), point);
|
||||
}
|
||||
let d = point - base;
|
||||
let radius = d.x.hypot(d.y);
|
||||
if radius <= 1e-10 || !radius.is_finite() || !d.length().is_finite() {
|
||||
return CmdResult::ReportError(
|
||||
crate::t!("Specify a finite nonzero parameter direction or radius.").into_owned(),
|
||||
);
|
||||
}
|
||||
match self.kind {
|
||||
Kind::Alignment => self.finish(point, point),
|
||||
Kind::Flip => {
|
||||
self.second = Some(point);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Kind::Rotation => {
|
||||
if self.second.is_some() {
|
||||
self.rotation_at(d.y.atan2(d.x))
|
||||
} else {
|
||||
self.second = Some(base + DVec3::X * radius);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if self.kind != Kind::Rotation || self.base.is_none() {
|
||||
return None;
|
||||
}
|
||||
let value = text.trim().parse::<f64>().ok()?;
|
||||
if !value.is_finite() {
|
||||
return Some(CmdResult::ReportError(
|
||||
crate::t!("Specify a finite radius or angle.").into_owned(),
|
||||
));
|
||||
}
|
||||
Some(if self.second.is_some() {
|
||||
self.rotation_at(value.rem_euclid(360.0).to_radians())
|
||||
} else if value > 1e-10 {
|
||||
self.on_point(self.base.unwrap() + DVec3::X * value)
|
||||
} else {
|
||||
CmdResult::ReportError(crate::t!("Specify a positive parameter radius.").into_owned())
|
||||
})
|
||||
}
|
||||
fn on_preview_wires(&mut self, point: DVec3) -> Vec<WireModel> {
|
||||
let Some(base) = self.base.filter(|_| point.is_finite()) else {
|
||||
return vec![];
|
||||
};
|
||||
let mut lines = vec![];
|
||||
if self.kind == Kind::Rotation {
|
||||
let radius = self
|
||||
.second
|
||||
.map(|p| (p - base).length())
|
||||
.unwrap_or((point - base).length());
|
||||
if !radius.is_finite() {
|
||||
return vec![];
|
||||
}
|
||||
let angle = (point.y - base.y).atan2(point.x - base.x);
|
||||
let end = base + DVec3::new(angle.cos(), angle.sin(), 0.0) * radius;
|
||||
lines.push(vec![base.to_array(), end.to_array()]);
|
||||
lines.push(
|
||||
(0..=64)
|
||||
.map(|i| {
|
||||
let a = std::f64::consts::TAU * i as f64 / 64.0;
|
||||
(base + DVec3::new(a.cos(), a.sin(), 0.0) * radius).to_array()
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
} else {
|
||||
lines.push(vec![
|
||||
base.to_array(),
|
||||
self.second.unwrap_or(point).to_array(),
|
||||
]);
|
||||
if let Some(end) = self.second {
|
||||
lines.push(vec![end.to_array(), point.to_array()]);
|
||||
}
|
||||
}
|
||||
lines
|
||||
.into_iter()
|
||||
.map(|points| {
|
||||
WireModel::solid_f64(
|
||||
"preview_orientation_parameter".into(),
|
||||
points,
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.kind == Kind::Rotation && self.second.is_some() {
|
||||
self.rotation_at(0.0)
|
||||
} else {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl CadCommand for VisibilityParameterCommand {
|
||||
fn prompt(&self) -> String {
|
||||
if self.choosing_type {
|
||||
crate::t!(
|
||||
"BPARAMETER Enter parameter type [Point/Linear/Polar/XY/Visibility] <Visibility>:"
|
||||
"BPARAMETER Enter parameter type [Point/Linear/Polar/XY/Rotation/Alignment/Flip/Visibility] <Visibility>:"
|
||||
)
|
||||
.into_owned()
|
||||
} else {
|
||||
@@ -43,6 +43,9 @@ impl CadCommand for VisibilityParameterCommand {
|
||||
CmdOption::new(crate::t!("Linear").as_ref(), "L"),
|
||||
CmdOption::new(crate::t!("Polar").as_ref(), "PO"),
|
||||
CmdOption::new(crate::t!("XY").as_ref(), "XY"),
|
||||
CmdOption::new(crate::t!("Rotation").as_ref(), "R"),
|
||||
CmdOption::new(crate::t!("Alignment").as_ref(), "A"),
|
||||
CmdOption::new(crate::t!("Flip").as_ref(), "F"),
|
||||
CmdOption::new(crate::t!("Visibility").as_ref(), "V"),
|
||||
]
|
||||
} else {
|
||||
@@ -68,13 +71,16 @@ impl CadCommand for VisibilityParameterCommand {
|
||||
"L" | "LINEAR" => CmdResult::Dispatch("BPARAMETER LINEAR".into()),
|
||||
"PO" | "POLAR" => CmdResult::Dispatch("BPARAMETER POLAR".into()),
|
||||
"XY" => CmdResult::Dispatch("BPARAMETER XY".into()),
|
||||
"R" | "ROTATION" => CmdResult::Dispatch("BPARAMETER ROTATION".into()),
|
||||
"A" | "ALIGNMENT" => CmdResult::Dispatch("BPARAMETER ALIGNMENT".into()),
|
||||
"F" | "FLIP" => CmdResult::Dispatch("BPARAMETER FLIP".into()),
|
||||
"P" | "POINT" => CmdResult::Dispatch("BPARAMETER POINT".into()),
|
||||
"" | "V" | "VISIBILITY" => {
|
||||
self.choosing_type = false;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
_ => CmdResult::ReportError(
|
||||
crate::t!("Supported parameter types: Point, Linear, Polar, XY, Visibility.")
|
||||
crate::t!("Supported parameter types: Point, Linear, Polar, XY, Rotation, Alignment, Flip, Visibility.")
|
||||
.into_owned(),
|
||||
),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user