forked from 1b-agent/opencadstudio-upstream
Commands now expose their step options as clickable buttons rendered inline beside the command-line prompt, so options need not be typed. Adds a CmdOption/options() API to CadCommand: each command returns the options for its current step; the command line renders them as uppercase buttons and a click feeds the keyword to the command exactly as if typed (an empty keyword submits like Enter). Construction commands become "central" — CIRCLE offers [3P/2P/TTR/TTT/ DIAMETER], ELLIPSE [ARC/AXIS], ARC [SCE/SCA/SEA/SER/CSA/3P], RECTANGLE [ROTATION/CENTER], POLYGON [CIRCUMSCRIBED/EDGE] — each keyword hands off to the existing dedicated variant command via CmdResult::Dispatch, instead of the variants only being reachable as isolated commands. Migrates the existing bracketed-option prompts (PLINE, PEDIT, SPLINE, SCALE, FILLET, CHAMFER, HATCH, GRADIENT, SOLID, POINT, RAY, XLINE, TRIM, EXTEND) to the same button API and strips the option text from the prompt strings, keeping value/coordinate hints. PEDIT's Width option now enters a value sub-step so the button acts on click. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
215 lines
7.3 KiB
Rust
215 lines
7.3 KiB
Rust
// Scale tool — ribbon definition + interactive command.
|
|
//
|
|
// Command: SCALE (SC)
|
|
// Requires at least one entity selected.
|
|
// Step 1: pick base (scale center)
|
|
// Step 2: specify scale factor — drag for a live preview (factor = cursor
|
|
// distance from base) or type a factor. A live ghost of the
|
|
// selection tracks the cursor from the first move onward.
|
|
//
|
|
// Reference scaling: type `R` at step 2 to define the factor as
|
|
// new-length / reference-length:
|
|
// Step 2a: specify reference length (pick a point or type a length)
|
|
// Step 2b: specify new length (pick a point or type a length)
|
|
|
|
use acadrust::Handle;
|
|
use glam::DVec3;
|
|
|
|
use crate::command::{CadCommand, CmdResult, EntityTransform};
|
|
use crate::modules::draw::defaults;
|
|
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
|
use crate::scene::model::wire_model::WireModel;
|
|
|
|
#[allow(dead_code)]
|
|
pub fn tool() -> ToolDef {
|
|
ToolDef {
|
|
id: "SCALE",
|
|
label: "Scale",
|
|
icon: IconKind::Svg(include_bytes!("../../../../assets/icons/scale.svg")),
|
|
event: ModuleEvent::Command("SCALE".to_string()),
|
|
}
|
|
}
|
|
|
|
enum Step {
|
|
Base,
|
|
/// Default flow: factor is the cursor distance from `base`.
|
|
Factor { base: DVec3 },
|
|
/// Reference flow: defining the reference length from `base`.
|
|
RefLen { base: DVec3 },
|
|
/// Reference flow: factor is `cursor_dist / ref_dist` from `base`.
|
|
RefNew { base: DVec3, ref_dist: f32 },
|
|
}
|
|
|
|
pub struct ScaleCommand {
|
|
handles: Vec<Handle>,
|
|
wire_models: Vec<WireModel>,
|
|
step: Step,
|
|
default_factor: f32,
|
|
}
|
|
|
|
impl ScaleCommand {
|
|
pub fn new(handles: Vec<Handle>, wire_models: Vec<WireModel>) -> Self {
|
|
Self {
|
|
handles,
|
|
wire_models,
|
|
step: Step::Base,
|
|
default_factor: defaults::get_scale_factor(),
|
|
}
|
|
}
|
|
|
|
/// Commit a uniform scale about `base` and end the command.
|
|
fn commit(&self, base: DVec3, factor: f32) -> CmdResult {
|
|
defaults::set_scale_factor(factor);
|
|
CmdResult::TransformSelected(
|
|
self.handles.clone(),
|
|
EntityTransform::Scale {
|
|
center: base,
|
|
factor,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
impl CadCommand for ScaleCommand {
|
|
fn name(&self) -> &'static str {
|
|
"SCALE"
|
|
}
|
|
|
|
fn prompt(&self) -> String {
|
|
match &self.step {
|
|
Step::Base => format!(
|
|
"SCALE Specify base point [{} objects]:",
|
|
self.handles.len()
|
|
),
|
|
Step::Factor { .. } => format!(
|
|
"SCALE Specify scale factor <{:.4}>:",
|
|
self.default_factor
|
|
),
|
|
Step::RefLen { .. } => {
|
|
"SCALE Specify reference length (pick a point or type a length):".into()
|
|
}
|
|
Step::RefNew { ref_dist, .. } => format!(
|
|
"SCALE Specify new length (pick a point or type a length) [ref={:.3}]:",
|
|
ref_dist
|
|
),
|
|
}
|
|
}
|
|
|
|
fn options(&self) -> Vec<crate::command::CmdOption> {
|
|
use crate::command::CmdOption;
|
|
match &self.step {
|
|
// The reference-scaling keyword is only offered at the factor step.
|
|
Step::Factor { .. } => vec![CmdOption::new("Reference", "R")],
|
|
_ => vec![],
|
|
}
|
|
}
|
|
|
|
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
|
match &self.step {
|
|
Step::Base => {
|
|
self.step = Step::Factor { base: pt };
|
|
CmdResult::NeedPoint
|
|
}
|
|
Step::Factor { base } => {
|
|
let base = *base;
|
|
let factor = base.distance(pt).max(1e-6) as f32;
|
|
self.commit(base, factor)
|
|
}
|
|
Step::RefLen { base } => {
|
|
let base = *base;
|
|
let ref_dist = base.distance(pt).max(1e-6) as f32;
|
|
self.step = Step::RefNew { base, ref_dist };
|
|
CmdResult::NeedPoint
|
|
}
|
|
Step::RefNew { base, ref_dist } => {
|
|
let base = *base;
|
|
let new_dist = base.distance(pt).max(1e-6) as f32;
|
|
self.commit(base, new_dist / *ref_dist)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_enter(&mut self) -> CmdResult {
|
|
// Enter at the factor step accepts the stored default factor.
|
|
if let Step::Factor { base } = &self.step {
|
|
let base = *base;
|
|
return self.commit(base, self.default_factor);
|
|
}
|
|
CmdResult::Cancel
|
|
}
|
|
fn on_escape(&mut self) -> CmdResult {
|
|
CmdResult::Cancel
|
|
}
|
|
|
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
|
let t = text.trim();
|
|
match &self.step {
|
|
Step::Factor { base } => {
|
|
let base = *base;
|
|
// `R` / `Reference` switches to reference scaling.
|
|
let low = t.to_ascii_lowercase();
|
|
if low == "r" || low == "reference" {
|
|
self.step = Step::RefLen { base };
|
|
return Some(CmdResult::NeedPoint);
|
|
}
|
|
let factor: f32 = t.replace(',', ".").parse().ok()?;
|
|
(factor > 0.0).then(|| self.commit(base, factor))
|
|
}
|
|
Step::RefLen { base } => {
|
|
let base = *base;
|
|
let ref_dist: f32 = t.replace(',', ".").parse().ok()?;
|
|
if ref_dist > 0.0 {
|
|
self.step = Step::RefNew { base, ref_dist };
|
|
return Some(CmdResult::NeedPoint);
|
|
}
|
|
None
|
|
}
|
|
Step::RefNew { base, ref_dist } => {
|
|
let (base, ref_dist) = (*base, *ref_dist);
|
|
let new_len: f32 = t.replace(',', ".").parse().ok()?;
|
|
(new_len > 0.0).then(|| self.commit(base, new_len / ref_dist))
|
|
}
|
|
Step::Base => None,
|
|
}
|
|
}
|
|
|
|
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
|
let (base, factor): (DVec3, f32) = match &self.step {
|
|
// Default flow: scale live by cursor distance from the base.
|
|
Step::Factor { base } => (*base, base.distance(pt).max(1e-6) as f32),
|
|
// Reference flow, new-length step: factor = cursor_dist / ref_dist.
|
|
Step::RefNew { base, ref_dist } => {
|
|
(*base, base.distance(pt).max(1e-6) as f32 / ref_dist)
|
|
}
|
|
// Reference-length step: rubber-band only, no factor defined yet.
|
|
Step::RefLen { base } => {
|
|
return vec![WireModel::solid(
|
|
"rubber_band".into(),
|
|
vec![
|
|
[base.x as f32, base.y as f32, base.z as f32],
|
|
[pt.x as f32, pt.y as f32, pt.z as f32],
|
|
],
|
|
WireModel::CYAN,
|
|
false,
|
|
)];
|
|
}
|
|
Step::Base => return vec![],
|
|
};
|
|
let mut out: Vec<WireModel> = self
|
|
.wire_models
|
|
.iter()
|
|
.map(|w| w.scaled(base.as_vec3(), factor))
|
|
.collect();
|
|
out.push(WireModel::solid(
|
|
"rubber_band".into(),
|
|
vec![
|
|
[base.x as f32, base.y as f32, base.z as f32],
|
|
[pt.x as f32, pt.y as f32, pt.z as f32],
|
|
],
|
|
WireModel::CYAN,
|
|
false,
|
|
));
|
|
out
|
|
}
|
|
}
|