fix(dimarc): complete merge integration

Preserve source-plane geometry and associative arc parameters while combining the latest properties and tolerance workflows.
This commit is contained in:
Hakan Seven committed 2026-08-27 01:00:23 +03:00
commit 6ef2d75e84
67 files changed
+4773 -883

No files matched your search

+104 -11
View File
@@ -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
}
}
+19 -1
View File
@@ -26,7 +26,7 @@ use acadrust::tables::BlockRecord;
use acadrust::types::Vector3;
use acadrust::{CadDocument, EntityType, Handle};
use crate::command::{CadCommand, CmdResult};
use crate::command::{CadCommand, CmdResult, WorkingPlane};
use crate::entities::curve::lwpolyline_world_xy;
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use glam::DVec3;
@@ -909,6 +909,24 @@ fn norm2(dx: f64, dy: f64, fx: f64, fy: f64) -> (f64, f64) {
}
fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
if let Dimension::Arc(arc) = dim {
if let Some((local_arc, normal)) =
crate::entities::dimension::arc_dimension_in_ocs(arc)
{
let (x_axis, y_axis) = crate::scene::view::transform::ocs_axes((
normal.x, normal.y, normal.z,
));
let plane = WorkingPlane::new(
DVec3::ZERO,
DVec3::new(x_axis.0, x_axis.1, x_axis.2),
DVec3::new(y_axis.0, y_axis.1, y_axis.2),
);
return explode_dimension(&Dimension::Arc(local_arc), doc)
.into_iter()
.map(|entity| plane.place_entity(entity))
.collect();
}
}
let base = dim.base();
let met = dim_metrics(dim, doc);
+74 -14
View File
@@ -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,
}