forked from 1b-agent/opencadstudio-upstream
fix(tolerance): complete workflow integration
Sync current main and companion codec changes before preserving unsupported frame rows, rendering in the entity plane, and sharing final layout metrics with placement preview.
This commit is contained in:
1 parent
66bf4b1d5f
commit
81e7add1dd
65 files changed
+3708
-934
No files matched your search
@@ -14,6 +14,7 @@ use glam::DVec3;
|
||||
use crate::t;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, EntityTransform};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
pub struct AlignCommand {
|
||||
state: AlignState,
|
||||
@@ -35,10 +36,16 @@ enum AlignState {
|
||||
}
|
||||
|
||||
impl AlignCommand {
|
||||
pub fn new() -> Self {
|
||||
pub fn with_selection(handles: Vec<Handle>) -> Self {
|
||||
let state = if handles.is_empty() {
|
||||
AlignState::Gathering
|
||||
} else {
|
||||
AlignState::Src1
|
||||
};
|
||||
|
||||
Self {
|
||||
state: AlignState::Gathering,
|
||||
handles: vec![],
|
||||
state,
|
||||
handles,
|
||||
src1: None,
|
||||
dst1: None,
|
||||
src2: None,
|
||||
@@ -66,11 +73,26 @@ impl CadCommand for AlignCommand {
|
||||
}
|
||||
AlignState::Dst2 => t!("ALIGN Specify 2nd destination point:").into_owned(),
|
||||
AlignState::AskScale => {
|
||||
t!("ALIGN Scale objects based on alignment points? [Y/N]:").into_owned()
|
||||
t!(
|
||||
"ALIGN Scale objects based on alignment points? [Yes / No]:"
|
||||
)
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
|
||||
match self.state {
|
||||
AlignState::AskScale => vec![
|
||||
CmdOption::new(t!("Yes").as_ref(), "Y"),
|
||||
CmdOption::new(t!("No").as_ref(), "N"),
|
||||
],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn is_selection_gathering(&self) -> bool {
|
||||
self.state == AlignState::Gathering
|
||||
}
|
||||
@@ -113,14 +135,17 @@ impl CadCommand for AlignCommand {
|
||||
if self.handles.is_empty() {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
|
||||
self.state = AlignState::Src1;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
AlignState::Src2 => {
|
||||
// Only 1 pair — pure translation
|
||||
// One alignment pair only: translation.
|
||||
match (self.src1, self.dst1) {
|
||||
(Some(s), Some(d)) => {
|
||||
let delta = d - s;
|
||||
|
||||
CmdResult::TransformSelected(
|
||||
self.handles.clone(),
|
||||
EntityTransform::Translate(delta),
|
||||
@@ -129,10 +154,10 @@ impl CadCommand for AlignCommand {
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
AlignState::AskScale => {
|
||||
// No scale (default N)
|
||||
self.compute_align(false)
|
||||
}
|
||||
|
||||
// Default option shown as <No>.
|
||||
AlignState::AskScale => self.compute_align(false),
|
||||
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
@@ -145,8 +170,76 @@ impl CadCommand for AlignCommand {
|
||||
if self.state != AlignState::AskScale {
|
||||
return None;
|
||||
}
|
||||
let scale = text.trim().to_uppercase().starts_with('Y');
|
||||
Some(self.compute_align(scale))
|
||||
|
||||
match text.trim().to_ascii_lowercase().as_str() {
|
||||
"y" | "yes" | "scale" => {
|
||||
Some(self.compute_align(true))
|
||||
}
|
||||
|
||||
"n" | "no" | "don't scale" | "dont scale" | "noscale" => {
|
||||
Some(self.compute_align(false))
|
||||
}
|
||||
|
||||
_ => Some(CmdResult::NeedPoint),
|
||||
}
|
||||
}
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
fn line(a: DVec3, b: DVec3, name: &str) -> WireModel {
|
||||
WireModel::solid(
|
||||
name.into(),
|
||||
vec![
|
||||
[a.x as f32, a.y as f32, a.z as f32],
|
||||
[b.x as f32, b.y as f32, b.z as f32],
|
||||
],
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
|
||||
// Keep the first completed alignment pair visible while defining
|
||||
// the second pair and while choosing the scale option.
|
||||
if let (Some(src1), Some(dst1)) = (self.src1, self.dst1) {
|
||||
match self.state {
|
||||
AlignState::Src2
|
||||
| AlignState::Dst2
|
||||
| AlignState::AskScale => {
|
||||
out.push(line(src1, dst1, "align_pair_1"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match self.state {
|
||||
// First source has been picked: stretch its reference line
|
||||
// to the cursor until the first destination is chosen.
|
||||
AlignState::Dst1 => {
|
||||
if let Some(src1) = self.src1 {
|
||||
out.push(line(src1, pt, "align_pair_1_preview"));
|
||||
}
|
||||
}
|
||||
|
||||
// Second source has been picked: stretch the second reference
|
||||
// line to the cursor until its destination is chosen.
|
||||
AlignState::Dst2 => {
|
||||
if let Some(src2) = self.src2 {
|
||||
out.push(line(src2, pt, "align_pair_2_preview"));
|
||||
}
|
||||
}
|
||||
|
||||
// Once both pairs are complete, keep both visible while
|
||||
// waiting for the Scale / No Scale decision.
|
||||
AlignState::AskScale => {
|
||||
if let (Some(src2), Some(dst2)) = (self.src2, self.dst2) {
|
||||
out.push(line(src2, dst2, "align_pair_2"));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -415,19 +415,19 @@ fn entity_wire_pts(e: &EntityType) -> Vec<[f32; 3]> {
|
||||
// ── Command implementation ─────────────────────────────────────────────────
|
||||
|
||||
enum Step {
|
||||
/// Classic first step (#418): type the offset distance, press Enter /
|
||||
/// Space to accept the last one, or choose Through mode.
|
||||
/// Type the offset distance, accept the previous value with Enter,
|
||||
/// choose Through, or pick the first point of a reference distance.
|
||||
Distance,
|
||||
/// Pick the object to offset. `locked == None` is "through" mode: the
|
||||
/// magnitude follows the cursor (perpendicular distance to the object).
|
||||
|
||||
/// First reference point has been picked; the second point defines
|
||||
/// the offset distance.
|
||||
ReferenceSecond { first: DVec3 },
|
||||
|
||||
SelectObject { locked: Option<f64> },
|
||||
|
||||
PickSide {
|
||||
/// The object(s) being offset — one from a pick, or the whole
|
||||
/// pre-selection when OFFSET starts with objects selected (#422).
|
||||
targets: Vec<EntityType>,
|
||||
locked: Option<f64>,
|
||||
/// Keep the side-pick step active and use each new result as the source
|
||||
/// for the next offset.
|
||||
multiple: bool,
|
||||
},
|
||||
}
|
||||
@@ -499,6 +499,18 @@ impl OffsetCommand {
|
||||
}
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
fn accept_distance(&mut self, distance: f64) -> CmdResult {
|
||||
let distance = distance.abs().max(1e-9);
|
||||
|
||||
defaults::set_offset_dist(distance);
|
||||
self.advance_from_distance(Some(distance));
|
||||
|
||||
let d = format!("{:.4}", distance);
|
||||
|
||||
CmdResult::ReportMeasurement(
|
||||
t!("OFFSET distance = %{d}", d = d).into_owned()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for OffsetCommand {
|
||||
@@ -510,7 +522,15 @@ impl CadCommand for OffsetCommand {
|
||||
match &self.step {
|
||||
Step::Distance => {
|
||||
let d = format!("{:.4}", defaults::get_offset_dist());
|
||||
t!("OFFSET Specify offset distance or [Through] <%{d}>:", d = d).into_owned()
|
||||
t!(
|
||||
"OFFSET Specify offset distance or first reference point [Through] <%{d}>:",
|
||||
d = d
|
||||
)
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
Step::ReferenceSecond { .. } => {
|
||||
t!("OFFSET Specify second reference point:").into_owned()
|
||||
}
|
||||
Step::SelectObject { .. } => {
|
||||
t!("OFFSET Select object to offset (Enter to finish):").into_owned()
|
||||
@@ -623,7 +643,9 @@ impl CadCommand for OffsetCommand {
|
||||
fn dyn_field(&self) -> crate::command::DynField {
|
||||
match self.step {
|
||||
Step::Distance | Step::PickSide { .. } => crate::command::DynField::Scalar,
|
||||
_ => crate::command::DynField::Point,
|
||||
|
||||
Step::ReferenceSecond { .. }
|
||||
| Step::SelectObject { .. } => crate::command::DynField::Point,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,9 +670,7 @@ impl CadCommand for OffsetCommand {
|
||||
return Some(self.advance_from_distance(None));
|
||||
}
|
||||
if let Some(d) = crate::entities::common::parse_typed_length(&t) {
|
||||
let d = d.abs().max(1e-9);
|
||||
defaults::set_offset_dist(d);
|
||||
return Some(self.advance_from_distance(Some(d)));
|
||||
return Some(self.accept_distance(d));
|
||||
}
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
@@ -698,12 +718,32 @@ impl CadCommand for OffsetCommand {
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match &self.step {
|
||||
Step::Distance => {
|
||||
self.step = Step::ReferenceSecond { first: pt };
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
|
||||
Step::ReferenceSecond { first } => {
|
||||
let distance = first.distance(pt);
|
||||
|
||||
if distance <= 1e-9 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
|
||||
return self.accept_distance(distance);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let (locked, targets, multiple) = match &self.step {
|
||||
Step::PickSide {
|
||||
locked,
|
||||
targets,
|
||||
multiple,
|
||||
} => (*locked, targets.clone(), *multiple),
|
||||
|
||||
_ => return CmdResult::NeedPoint,
|
||||
};
|
||||
// Each target offsets by its own through-distance (or the locked
|
||||
@@ -745,6 +785,26 @@ impl CadCommand for OffsetCommand {
|
||||
}
|
||||
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
if let Step::ReferenceSecond { first } = &self.step {
|
||||
return vec![WireModel::solid(
|
||||
"offset_reference_distance".into(),
|
||||
vec![
|
||||
[
|
||||
first.x as f32,
|
||||
first.y as f32,
|
||||
first.z as f32,
|
||||
],
|
||||
[
|
||||
pt.x as f32,
|
||||
pt.y as f32,
|
||||
pt.z as f32,
|
||||
],
|
||||
],
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
)];
|
||||
}
|
||||
|
||||
let (locked, targets) = match &self.step {
|
||||
Step::PickSide { locked, targets, .. } => (*locked, targets.clone()),
|
||||
_ => return vec![],
|
||||
@@ -779,7 +839,7 @@ impl CadCommand for OffsetCommand {
|
||||
// the "repeat the same value with just Space" flow (#418).
|
||||
Step::Distance => {
|
||||
let d = defaults::get_offset_dist();
|
||||
self.advance_from_distance(Some(d.abs().max(1e-9)))
|
||||
self.accept_distance(d)
|
||||
}
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user