forked from 1b-agent/opencadstudio-upstream
feat:(BEDIT) : add Polar Stretch block action authoring and evaluation
Enable Polar Stretch actions through the authoring palette and BACTION command. Persist native action data, edit target selections and frames, and evaluate parameter-driven displacement on block references.
This commit is contained in:
1 parent
4aa4b72d17
commit
bbf5afe53d
8 files changed
+263
-32
No files matched your search
@@ -95,7 +95,7 @@ impl OpenCADStudio {
|
||||
};
|
||||
if !args.is_empty() && kind.is_none() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("Supported actions: Move, Scale, Stretch, Rotate, Flip, Array. Other action types are not wired yet.")
|
||||
crate::t!("Supported actions: Move, Scale, Stretch, Polar Stretch, Rotate, Flip, Array. Other action types are not wired yet.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
|
||||
@@ -181,7 +181,10 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if matches!(cmd.split(' ').next(), Some("BACTION" | "BACTIONTOOL" | "BARRAY")) {
|
||||
if matches!(
|
||||
cmd.split(' ').next(),
|
||||
Some("BACTION" | "BACTIONTOOL" | "BARRAY")
|
||||
) {
|
||||
let args = if cmd == "BARRAY" || cmd.starts_with("BARRAY ") {
|
||||
"ARRAY"
|
||||
} else {
|
||||
@@ -190,7 +193,7 @@ impl OpenCADStudio {
|
||||
if !args.is_empty() && crate::scene::block_actions::Kind::parse(args).is_none() {
|
||||
self.command_line.push_error(
|
||||
crate::t!(
|
||||
"Supported actions: Move, Scale, Stretch, Rotate, Flip, Array. Other action types are not wired yet."
|
||||
"Supported actions: Move, Scale, Stretch, Polar Stretch, Rotate, Flip, Array. Other action types are not wired yet."
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
|
||||
@@ -416,6 +416,7 @@ impl OpenCADStudio {
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Rotate)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Flip)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Stretch)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::PolarStretch)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Array)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl ActionCommand {
|
||||
);
|
||||
}
|
||||
self.parameter = Some(p.handle);
|
||||
self.selecting = self.kind != Some(Kind::Stretch);
|
||||
self.selecting = !self.kind.is_some_and(actions::needs_frame);
|
||||
CmdResult::DeselectAndContinue
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,10 @@ impl CadCommand for ActionCommand {
|
||||
}
|
||||
fn prompt(&self) -> String {
|
||||
if self.kind.is_none() {
|
||||
return crate::t!("BACTION Enter action type [Move/Scale/Stretch/Rotate/Flip/Array]:")
|
||||
.into_owned();
|
||||
return crate::t!(
|
||||
"BACTION Enter action type [Move/Scale/Stretch/Polar Stretch/Rotate/Flip/Array]:"
|
||||
)
|
||||
.into_owned();
|
||||
}
|
||||
if self.parameter.is_none() {
|
||||
return crate::tf!(
|
||||
@@ -88,11 +90,11 @@ impl CadCommand for ActionCommand {
|
||||
)
|
||||
.into_owned();
|
||||
}
|
||||
if self.kind == Some(Kind::Stretch) && self.frame.is_empty() {
|
||||
return crate::t!("BACTION Specify first corner of stretch frame:").into_owned();
|
||||
if self.kind.is_some_and(actions::needs_frame) && self.frame.is_empty() {
|
||||
return crate::t!("BACTION Specify first corner of action frame:").into_owned();
|
||||
}
|
||||
if self.kind == Some(Kind::Stretch) && self.frame.len() == 1 {
|
||||
return crate::t!("BACTION Specify opposite corner of stretch frame:").into_owned();
|
||||
if self.kind.is_some_and(actions::needs_frame) && self.frame.len() == 1 {
|
||||
return crate::t!("BACTION Specify opposite corner of action frame:").into_owned();
|
||||
}
|
||||
if self.selecting {
|
||||
return crate::t!("BACTION Select objects for the action, then press Enter:")
|
||||
@@ -122,6 +124,7 @@ impl CadCommand for ActionCommand {
|
||||
CmdOption::new("Move", "Move"),
|
||||
CmdOption::new("Scale", "Scale"),
|
||||
CmdOption::new("Stretch", "Stretch"),
|
||||
CmdOption::new("Polar Stretch", "Polar Stretch"),
|
||||
CmdOption::new("Rotate", "Rotate"),
|
||||
CmdOption::new("Flip", "Flip"),
|
||||
CmdOption::new("Array", "Array"),
|
||||
@@ -137,7 +140,8 @@ impl CadCommand for ActionCommand {
|
||||
CmdResult::NeedPoint
|
||||
} else {
|
||||
CmdResult::ReportError(
|
||||
crate::t!("Choose Move, Scale, Stretch, Rotate, Flip or Array.").into_owned(),
|
||||
crate::t!("Choose Move, Scale, Stretch, Polar Stretch, Rotate, Flip or Array.")
|
||||
.into_owned(),
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -224,7 +228,7 @@ impl CadCommand for ActionCommand {
|
||||
)
|
||||
});
|
||||
}
|
||||
if self.kind == Some(Kind::Stretch) && self.frame.len() < 2 {
|
||||
if self.kind.is_some_and(actions::needs_frame) && self.frame.len() < 2 {
|
||||
let plane = self
|
||||
.candidates
|
||||
.iter()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Edit native action selections without replacing their graph nodes.
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult, InputKind, PointPickContext};
|
||||
use crate::scene::block_actions::{self as actions, Kind};
|
||||
use crate::scene::block_actions as actions;
|
||||
use codec::objects::ObjectType;
|
||||
use codec::{CadDocument, Handle};
|
||||
use glam::DVec3;
|
||||
@@ -48,7 +48,7 @@ impl ActionSetCommand {
|
||||
name: a.element.name.clone(),
|
||||
at: actions::v(a.display_location),
|
||||
targets: a.dependencies.clone(),
|
||||
stretch: kind == Kind::Stretch,
|
||||
stretch: actions::needs_frame(kind),
|
||||
expected: doc.objects[&handle].clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -108,9 +108,9 @@ impl CadCommand for ActionSetCommand {
|
||||
} else if self.mode.is_none() {
|
||||
"BACTIONSET [New/Add/Remove] <New>:"
|
||||
} else if matches!(self.mode, Some(Mode::Frame)) && self.frame.is_empty() {
|
||||
"BACTIONSET Specify first corner of replacement stretch frame:"
|
||||
"BACTIONSET Specify first corner of replacement action frame:"
|
||||
} else if matches!(self.mode, Some(Mode::Frame)) {
|
||||
"BACTIONSET Specify opposite corner of replacement stretch frame:"
|
||||
"BACTIONSET Specify opposite corner of replacement action frame:"
|
||||
} else {
|
||||
"BACTIONSET Select objects, then press Enter:"
|
||||
})
|
||||
|
||||
+143
-12
@@ -3,14 +3,14 @@ use super::block_authoring::{self as authoring, *};
|
||||
use codec::objects::{
|
||||
BlockAction, BlockActionOffsets, BlockActionWithBasePoint, BlockArrayAction,
|
||||
BlockBasePointAction, BlockConnection, BlockElement, BlockEvalValue, BlockFlipAction,
|
||||
BlockMoveAction, BlockStretchAction, DynamicBlockData, ObjectType,
|
||||
BlockMoveAction, BlockPolarStretchAction, BlockStretchAction, DynamicBlockData, ObjectType,
|
||||
};
|
||||
use codec::types::{Transform, Vector3};
|
||||
use codec::{CadDocument, EntityType, Handle};
|
||||
use glam::DVec3;
|
||||
pub(crate) mod instance;
|
||||
mod stretch;
|
||||
mod representation;
|
||||
mod stretch;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -21,16 +21,18 @@ pub enum Kind {
|
||||
Rotate,
|
||||
Flip,
|
||||
Stretch,
|
||||
PolarStretch,
|
||||
Array,
|
||||
}
|
||||
impl Kind {
|
||||
pub(crate) fn parse(s: &str) -> Option<Self> {
|
||||
match s.to_ascii_uppercase().as_str() {
|
||||
match s.trim().to_ascii_uppercase().as_str() {
|
||||
"MOVE" | "M" => Some(Self::Move),
|
||||
"SCALE" | "S" => Some(Self::Scale),
|
||||
"ROTATE" | "R" => Some(Self::Rotate),
|
||||
"FLIP" | "F" => Some(Self::Flip),
|
||||
"STRETCH" | "ST" => Some(Self::Stretch),
|
||||
"POLARSTRETCH" | "POLAR STRETCH" | "PS" => Some(Self::PolarStretch),
|
||||
"ARRAY" | "A" => Some(Self::Array),
|
||||
_ => None,
|
||||
}
|
||||
@@ -42,11 +44,17 @@ impl Kind {
|
||||
Self::Rotate => "Rotate",
|
||||
Self::Flip => "Flip",
|
||||
Self::Stretch => "Stretch",
|
||||
Self::PolarStretch => "Polar Stretch",
|
||||
Self::Array => "Array",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Actions that author a rectangular frame before selecting objects.
|
||||
pub(crate) fn needs_frame(kind: Kind) -> bool {
|
||||
matches!(kind, Kind::Stretch | Kind::PolarStretch)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Parameter {
|
||||
pub handle: Handle,
|
||||
@@ -58,6 +66,7 @@ pub(crate) struct Parameter {
|
||||
pub linear: bool,
|
||||
pub point: bool,
|
||||
pub xy: bool,
|
||||
pub polar: bool,
|
||||
pub rotation: bool,
|
||||
pub flip: bool,
|
||||
pub reference: DVec3,
|
||||
@@ -87,6 +96,7 @@ pub(crate) fn compatible(p: &Parameter, kind: Kind) -> bool {
|
||||
match kind {
|
||||
Kind::Move => !p.rotation && !p.flip,
|
||||
Kind::Stretch => !p.rotation && !p.flip && !p.point,
|
||||
Kind::PolarStretch => p.polar,
|
||||
Kind::Scale => !p.rotation && !p.flip && !p.point && !p.xy,
|
||||
Kind::Rotate => p.rotation,
|
||||
Kind::Flip => p.flip,
|
||||
@@ -208,6 +218,7 @@ pub(crate) fn parameters(doc: &CadDocument, definition: Handle, kind: Kind) -> V
|
||||
linear: false,
|
||||
point: true,
|
||||
xy: false,
|
||||
polar: false,
|
||||
rotation: false,
|
||||
flip: false,
|
||||
reference: DVec3::ZERO,
|
||||
@@ -218,10 +229,14 @@ pub(crate) fn parameters(doc: &CadDocument, definition: Handle, kind: Kind) -> V
|
||||
if matches!(kind, Kind::Rotate | Kind::Flip) {
|
||||
continue;
|
||||
}
|
||||
let polar = p.kind == super::block_distance_parameter::Kind::Polar;
|
||||
let xy = p.kind == super::block_distance_parameter::Kind::XY;
|
||||
if kind == Kind::Scale && xy {
|
||||
continue;
|
||||
}
|
||||
if kind == Kind::PolarStretch && !polar {
|
||||
continue;
|
||||
}
|
||||
result.push(Parameter {
|
||||
handle: p.handle,
|
||||
id: p.native.parameter.element.eval.node_id,
|
||||
@@ -232,6 +247,7 @@ pub(crate) fn parameters(doc: &CadDocument, definition: Handle, kind: Kind) -> V
|
||||
linear: p.kind == super::block_distance_parameter::Kind::Linear,
|
||||
point: false,
|
||||
xy,
|
||||
polar,
|
||||
rotation: false,
|
||||
flip: false,
|
||||
reference: DVec3::ZERO,
|
||||
@@ -255,6 +271,7 @@ pub(crate) fn parameters(doc: &CadDocument, definition: Handle, kind: Kind) -> V
|
||||
linear: false,
|
||||
point: false,
|
||||
xy: false,
|
||||
polar: false,
|
||||
rotation: kind == Kind::Rotate,
|
||||
flip: kind == Kind::Flip,
|
||||
reference: v(p.reference),
|
||||
@@ -361,7 +378,9 @@ pub(crate) fn create(
|
||||
targets: &[Handle],
|
||||
location: DVec3,
|
||||
) -> Result<Handle, &'static str> {
|
||||
create_with_options(doc, definition, kind, parameter, targets, location, None, None)
|
||||
create_with_options(
|
||||
doc, definition, kind, parameter, targets, location, None, None,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -396,7 +415,9 @@ pub(crate) fn create_with_frame(
|
||||
location: DVec3,
|
||||
frame: Option<[DVec3; 2]>,
|
||||
) -> Result<Handle, &'static str> {
|
||||
create_with_options(doc, definition, kind, parameter, targets, location, frame, None)
|
||||
create_with_options(
|
||||
doc, definition, kind, parameter, targets, location, frame, None,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn create_with_options(
|
||||
@@ -458,6 +479,19 @@ pub(crate) fn create_with_options(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let polar_binding = if kind == Kind::PolarStretch {
|
||||
if (location.z - p.end.z).abs() > 1e-8 {
|
||||
return Err("Place the Polar Stretch marker in the parameter's XY plane.");
|
||||
}
|
||||
Some(stretch::bind(
|
||||
doc,
|
||||
targets,
|
||||
frame.ok_or("Specify a Polar Stretch frame before selecting objects.")?,
|
||||
p.end.z,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut staged = doc.clone();
|
||||
let (graph, slots) = authoring::prepare(&mut staged, definition)?;
|
||||
let (dxf, cpp) = match kind {
|
||||
@@ -466,6 +500,7 @@ pub(crate) fn create_with_options(
|
||||
Kind::Rotate => ("BLOCKROTATEACTION", "AcDbBlockRotationAction"),
|
||||
Kind::Flip => ("BLOCKFLIPACTION", "AcDbBlockFlipAction"),
|
||||
Kind::Stretch => ("BLOCKSTRETCHACTION", "AcDbBlockStretchAction"),
|
||||
Kind::PolarStretch => ("BLOCKPOLARSTRETCHACTION", "AcDbBlockPolarStretchAction"),
|
||||
Kind::Array => ("BLOCKARRAYACTION", "AcDbBlockArrayAction"),
|
||||
};
|
||||
register_classes(&mut staged, &[(dxf, cpp, 55)]);
|
||||
@@ -482,7 +517,7 @@ pub(crate) fn create_with_options(
|
||||
eed_1071: 0,
|
||||
},
|
||||
display_location: native(location),
|
||||
dependencies,
|
||||
dependencies: dependencies.clone(),
|
||||
action_ids: vec![],
|
||||
};
|
||||
let connection = |name: &str| BlockConnection {
|
||||
@@ -516,6 +551,31 @@ pub(crate) fn create_with_options(
|
||||
},
|
||||
})
|
||||
}
|
||||
Kind::PolarStretch => {
|
||||
let (points, bindings) = polar_binding.unwrap();
|
||||
DynamicBlockData::PolarStretchAction(BlockPolarStretchAction {
|
||||
action,
|
||||
connections: std::array::from_fn(|i| BlockConnection {
|
||||
code: p.id,
|
||||
name: [
|
||||
"BaseXDelta",
|
||||
"BaseYDelta",
|
||||
"Base",
|
||||
"End",
|
||||
"UpdatedBase",
|
||||
"UpdatedEnd",
|
||||
][i]
|
||||
.into(),
|
||||
}),
|
||||
points,
|
||||
handles: vec![],
|
||||
bindings,
|
||||
codes: vec![],
|
||||
distance_multiplier: 1.,
|
||||
angle_offset: 0.,
|
||||
extra: vec![],
|
||||
})
|
||||
}
|
||||
Kind::Move => DynamicBlockData::MoveAction(BlockMoveAction {
|
||||
action,
|
||||
connections: if p.point {
|
||||
@@ -575,7 +635,6 @@ pub(crate) fn create_with_options(
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
|
||||
/// Retarget an existing native action, retaining identity, graph and options.
|
||||
pub(crate) fn retarget(
|
||||
doc: &mut CadDocument,
|
||||
@@ -642,6 +701,33 @@ pub(crate) fn retarget(
|
||||
}
|
||||
a.action.dependencies = targets;
|
||||
}
|
||||
DynamicBlockData::PolarStretchAction(a) => {
|
||||
stretch::validate_polar(doc, a)?;
|
||||
let plane = parameters(doc, definition, Kind::PolarStretch)
|
||||
.into_iter()
|
||||
.find(|p| p.id == id)
|
||||
.ok_or("The Polar Stretch parameter is unavailable.")?
|
||||
.end
|
||||
.z;
|
||||
if let Some(corners) = frame {
|
||||
let (points, bindings) = stretch::bind(doc, &targets, corners, plane)?;
|
||||
a.points = points;
|
||||
a.bindings = bindings;
|
||||
} else {
|
||||
a.bindings.retain(|b| targets.contains(&b.handle));
|
||||
let added = targets
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|h| !old_targets.contains(h))
|
||||
.collect::<Vec<_>>();
|
||||
let frame = a.points.clone();
|
||||
a.bindings
|
||||
.extend(stretch::bind_existing(doc, &added, &frame, plane)?);
|
||||
a.bindings.sort_by_key(|b| b.handle);
|
||||
}
|
||||
a.handles.retain(|h| targets.contains(h));
|
||||
a.action.dependencies = targets;
|
||||
}
|
||||
DynamicBlockData::MoveAction(a) => a.action.dependencies = targets,
|
||||
DynamicBlockData::FlipAction(a) => a.action.dependencies = targets,
|
||||
DynamicBlockData::ScaleAction(a) | DynamicBlockData::RotateAction(a) => {
|
||||
@@ -650,8 +736,8 @@ pub(crate) fn retarget(
|
||||
DynamicBlockData::ArrayAction(a) => a.action.dependencies = targets,
|
||||
_ => return Err("This action's selection cannot be edited yet."),
|
||||
}
|
||||
if frame.is_some() && kind != Kind::Stretch {
|
||||
return Err("Only Stretch actions have a stretch frame.");
|
||||
if frame.is_some() && !needs_frame(kind) {
|
||||
return Err("Only Stretch actions have an action frame.");
|
||||
}
|
||||
if staged == *doc {
|
||||
return Ok(false);
|
||||
@@ -689,6 +775,12 @@ pub(crate) fn actions(
|
||||
DynamicBlockData::StretchAction(a) => {
|
||||
Some((o.handle, &a.action, Kind::Stretch, a.connections[0].code))
|
||||
}
|
||||
DynamicBlockData::PolarStretchAction(a) => Some((
|
||||
o.handle,
|
||||
&a.action,
|
||||
Kind::PolarStretch,
|
||||
a.connections[0].code,
|
||||
)),
|
||||
DynamicBlockData::FlipAction(a) => {
|
||||
Some((o.handle, &a.action, Kind::Flip, a.connections[0].code))
|
||||
}
|
||||
@@ -730,6 +822,11 @@ pub(crate) fn prune_erased_targets(doc: &mut CadDocument, definition: Handle) {
|
||||
a.handles.retain(|b| live.contains(&b.handle));
|
||||
&mut a.action
|
||||
}
|
||||
DynamicBlockData::PolarStretchAction(a) => {
|
||||
a.handles.retain(|h| live.contains(h));
|
||||
a.bindings.retain(|b| live.contains(&b.handle));
|
||||
&mut a.action
|
||||
}
|
||||
DynamicBlockData::ScaleAction(a) | DynamicBlockData::RotateAction(a) => {
|
||||
&mut a.action.action
|
||||
}
|
||||
@@ -744,6 +841,9 @@ pub(crate) fn frame_outline(doc: &CadDocument, handle: Handle) -> Vec<Vector3> {
|
||||
return vec![];
|
||||
};
|
||||
let DynamicBlockData::StretchAction(a) = &o.data else {
|
||||
if let DynamicBlockData::PolarStretchAction(a) = &o.data {
|
||||
return stretch::outline(&a.points, a.action.display_location.z);
|
||||
}
|
||||
return vec![];
|
||||
};
|
||||
stretch::outline(&a.points, a.action.display_location.z)
|
||||
@@ -776,6 +876,17 @@ pub(crate) fn reframe(doc: &mut CadDocument, definition: Handle, t: &Transform)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
DynamicBlockData::PolarStretchAction(a) => {
|
||||
let plane = a.action.display_location.z;
|
||||
a.action.display_location = t.apply(a.action.display_location);
|
||||
a.points = stretch::outline(&a.points, plane)
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let p = t.apply(p);
|
||||
codec::types::Vector2::new(p.x, p.y)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
DynamicBlockData::FlipAction(a) => {
|
||||
a.action.display_location = t.apply(a.action.display_location)
|
||||
}
|
||||
@@ -966,7 +1077,11 @@ fn evaluate_impl(
|
||||
for h in extra_handles {
|
||||
staged.remove_entity(h);
|
||||
}
|
||||
if let Some(record) = staged.block_records.iter_mut().find(|b| b.handle == definition) {
|
||||
if let Some(record) = staged
|
||||
.block_records
|
||||
.iter_mut()
|
||||
.find(|b| b.handle == definition)
|
||||
{
|
||||
record.entity_handles.retain(|h| {
|
||||
baseline_members.contains(h)
|
||||
|| *h == record.block_entity_handle
|
||||
@@ -984,6 +1099,14 @@ fn evaluate_impl(
|
||||
unreachable!()
|
||||
};
|
||||
stretch::validate(baseline, s)?;
|
||||
} else if *kind == Kind::PolarStretch {
|
||||
let ObjectType::DynamicBlock(o) = &baseline.objects[h] else {
|
||||
unreachable!()
|
||||
};
|
||||
let DynamicBlockData::PolarStretchAction(s) = &o.data else {
|
||||
unreachable!()
|
||||
};
|
||||
stretch::validate_polar(baseline, s)?;
|
||||
}
|
||||
affected.extend(a.dependencies.iter().copied());
|
||||
}
|
||||
@@ -1012,7 +1135,7 @@ fn evaluate_impl(
|
||||
}
|
||||
let end = current(&staged, &q);
|
||||
let transform = match kind {
|
||||
Kind::Move | Kind::Stretch => {
|
||||
Kind::Move | Kind::Stretch | Kind::PolarStretch => {
|
||||
crate::command::EntityTransform::Translate(end - q.end)
|
||||
}
|
||||
Kind::Rotate => {
|
||||
@@ -1134,6 +1257,15 @@ fn evaluate_impl(
|
||||
}
|
||||
if let DynamicBlockData::StretchAction(s) = &o.data {
|
||||
stretch::apply(&mut entity, s, end - q.end)?;
|
||||
} else if let DynamicBlockData::PolarStretchAction(s) = &o.data {
|
||||
if s.connections.iter().any(|c| c.code != q.id) {
|
||||
return Err("This Polar Stretch action uses a different parameter binding.");
|
||||
}
|
||||
let direction = q.end - q.base;
|
||||
if direction.length() <= 1e-9 {
|
||||
return Err("The Polar parameter needs a nonzero distance.");
|
||||
}
|
||||
stretch::apply_polar(&mut entity, &s.bindings, q.base, q.end, end)?;
|
||||
} else {
|
||||
super::view::dispatch::apply_transform(&mut entity, &transform);
|
||||
}
|
||||
@@ -1262,7 +1394,6 @@ fn evaluate_impl(
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
fn supported_parameter(doc: &CadDocument, handle: Handle) -> bool {
|
||||
let Some(ObjectType::DynamicBlock(o)) = doc.objects.get(&handle) else {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! A native Stretch action freezes its vertex membership at authoring time.
|
||||
use super::*;
|
||||
use codec::objects::{BlockStretchAction, BlockStretchHandle};
|
||||
use codec::objects::{BlockPolarStretchAction, BlockStretchAction, BlockStretchHandle};
|
||||
use codec::types::Vector2;
|
||||
|
||||
pub(crate) fn control_points(e: &EntityType) -> Result<Vec<DVec3>, &'static str> {
|
||||
@@ -160,6 +160,75 @@ pub(crate) fn validate(doc: &CadDocument, a: &BlockStretchAction) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_polar(
|
||||
doc: &CadDocument,
|
||||
a: &BlockPolarStretchAction,
|
||||
) -> Result<(), &'static str> {
|
||||
let connection_code = a.connections[0].code;
|
||||
let connections = [
|
||||
"BaseXDelta",
|
||||
"BaseYDelta",
|
||||
"Base",
|
||||
"End",
|
||||
"UpdatedBase",
|
||||
"UpdatedEnd",
|
||||
]
|
||||
.map(|name| codec::objects::BlockConnection {
|
||||
code: connection_code,
|
||||
name: name.into(),
|
||||
});
|
||||
if a.distance_multiplier != 1.
|
||||
|| a.angle_offset != 0.
|
||||
|| a.connections != connections
|
||||
|| !a.codes.is_empty()
|
||||
|| !a.extra.is_empty()
|
||||
|| !a.action.action_ids.is_empty()
|
||||
|| a.points.len() < 2
|
||||
|| a.points
|
||||
.iter()
|
||||
.any(|p| !p.x.is_finite() || !p.y.is_finite())
|
||||
{
|
||||
return Err("This Polar Stretch action uses options not supported yet.");
|
||||
}
|
||||
let mut handles = a.handles.clone();
|
||||
handles.sort();
|
||||
handles.dedup();
|
||||
if handles != a.handles || handles.iter().any(|h| !a.action.dependencies.contains(h)) {
|
||||
return Err("The Polar Stretch action has invalid target bindings.");
|
||||
}
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
for binding in &a.bindings {
|
||||
if !a.action.dependencies.contains(&binding.handle) || !seen.insert(binding.handle) {
|
||||
return Err("The Polar Stretch action has invalid vertex bindings.");
|
||||
}
|
||||
let count = control_points(
|
||||
doc.get_entity(binding.handle)
|
||||
.ok_or("A Polar Stretch target is unavailable.")?,
|
||||
)?
|
||||
.len();
|
||||
let unique = binding
|
||||
.indexes
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
if unique.len() != binding.indexes.len()
|
||||
|| binding
|
||||
.indexes
|
||||
.iter()
|
||||
.any(|i| *i < 0 || *i as usize >= count)
|
||||
{
|
||||
return Err("A Polar Stretch vertex binding no longer matches its object.");
|
||||
}
|
||||
}
|
||||
for h in &a.action.dependencies {
|
||||
control_points(
|
||||
doc.get_entity(*h)
|
||||
.ok_or("A Polar Stretch target is unavailable.")?,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn apply(
|
||||
e: &mut EntityType,
|
||||
a: &BlockStretchAction,
|
||||
@@ -224,6 +293,26 @@ pub(crate) fn apply(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply the authored polar displacement to only the vertices frozen in the
|
||||
/// Polar Stretch bindings; targets without a partial binding move as a whole.
|
||||
pub(crate) fn apply_polar(
|
||||
e: &mut EntityType,
|
||||
bindings: &[BlockStretchHandle],
|
||||
base: DVec3,
|
||||
old_end: DVec3,
|
||||
new_end: DVec3,
|
||||
) -> Result<(), &'static str> {
|
||||
let (old_vec, new_vec) = (old_end - base, new_end - base);
|
||||
if old_vec.length() <= 1e-9 || new_vec.length() <= 1e-9 {
|
||||
return Err("The Polar parameter needs a nonzero distance.");
|
||||
}
|
||||
let action = BlockStretchAction {
|
||||
handles: bindings.to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
apply(e, &action, new_end - old_end)
|
||||
}
|
||||
|
||||
pub(crate) fn outline(points: &[Vector2], z: f64) -> Vec<Vector3> {
|
||||
let points = if points.len() == 2 {
|
||||
let (a, b) = (points[0], points[1]);
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::ui::dock::{self, Chrome, PanelId, VerticalLabel};
|
||||
use crate::ui::icons;
|
||||
use crate::ui::ribbon::{tooltip_content, tooltip_style};
|
||||
use iced::widget::{
|
||||
button, canvas, column, container, rule, row, scrollable, stack, text, tooltip, Space,
|
||||
button, canvas, column, container, row, rule, scrollable, stack, text, tooltip, Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
@@ -199,6 +199,7 @@ fn catalog(
|
||||
rotate_error: Option<&'static str>,
|
||||
flip_action_error: Option<&'static str>,
|
||||
stretch_action_error: Option<&'static str>,
|
||||
polar_stretch_action_error: Option<&'static str>,
|
||||
array_action_error: Option<&'static str>,
|
||||
) -> Element<'static, Message> {
|
||||
let (entries, unavailable) = match tab {
|
||||
@@ -221,8 +222,8 @@ fn catalog(
|
||||
let orientation_error = if *label=="Flip" {flip_error} else {point_error};
|
||||
let single=tab==Tab::Parameters&&matches!(*label,"Lookup"|"Basepoint");
|
||||
let lookup_action=tab==Tab::Actions&&*label=="Lookup";
|
||||
let geometry_action=tab==Tab::Actions&&matches!(*label,"Move"|"Scale"|"Stretch"|"Rotate"|"Flip"|"Array");
|
||||
let action_error=match *label {"Scale"=>scale_error,"Stretch"=>stretch_action_error,"Rotate"=>rotate_error,"Flip"=>flip_action_error,"Array"=>array_action_error,_=>move_error};
|
||||
let geometry_action=tab==Tab::Actions&&matches!(*label,"Move"|"Scale"|"Stretch"|"Polar Stretch"|"Rotate"|"Flip"|"Array");
|
||||
let action_error=match *label {"Scale"=>scale_error,"Stretch"=>stretch_action_error,"Polar Stretch"=>polar_stretch_action_error,"Rotate"=>rotate_error,"Flip"=>flip_action_error,"Array"=>array_action_error,_=>move_error};
|
||||
let enabled = (geometry_action&&action_error.is_none())||(single&&point_error.is_none())||(lookup_action&&lookup_error.is_none())||(orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
|
||||
let base = if enabled {
|
||||
icons::semantic(icon, 28.0)
|
||||
@@ -408,6 +409,7 @@ pub fn view(
|
||||
rotate_error: Option<&'static str>,
|
||||
flip_action_error: Option<&'static str>,
|
||||
stretch_action_error: Option<&'static str>,
|
||||
polar_stretch_action_error: Option<&'static str>,
|
||||
array_action_error: Option<&'static str>,
|
||||
) -> Element<'static, Message> {
|
||||
let tabs = column(Tab::ALL.into_iter().map(|category| {
|
||||
@@ -469,6 +471,7 @@ pub fn view(
|
||||
rotate_error,
|
||||
flip_action_error,
|
||||
stretch_action_error,
|
||||
polar_stretch_action_error,
|
||||
array_action_error,
|
||||
))
|
||||
.width(Fill)
|
||||
|
||||
Reference in new issue
Block a user